• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <ejdb2.h>
2 #define NAPI_EXPERIMENTAL
3 #include "node_api.h"
4 #include <stdlib.h>
5 #include <string.h>
6 #include <pthread.h>
7 #include <errno.h>
8 #include <assert.h>
9 #include <inttypes.h>
10 
11 #define STR_HELPER(x) #x
12 #define STR(x)        STR_HELPER(x)
13 
14 static void jn_resultset_tsf(
15   napi_env   env,
16   napi_value js_add_stream,
17   void      *context,
18   void      *data);
19 static bool jn_throw_error(napi_env env, iwrc rc, const char *location, const char *msg);
20 static napi_value jn_create_error(napi_env env, iwrc rc, const char *location, const char *msg);
21 
jn_is_exception_pending(napi_env env)22 IW_INLINE bool jn_is_exception_pending(napi_env env) {
23   bool rv = false;
24   napi_is_exception_pending(env, &rv);
25   return rv;
26 }
27 
28 #define JNTHROW(env, rc, message) \
29   jn_throw_error(env, rc, "ejdb2_node.c" ":" STR(__LINE__), message)
30 
31 #define JNTHROW_LAST(env) do {                         \
32     const napi_extended_error_info *info = 0;          \
33     napi_get_last_error_info((env), &info);            \
34     if (info) JNTHROW((env), 0, info->error_message);  \
35 } while (0)
36 
37 #define JNCHECK(ns, env) do {                   \
38     if (ns && ns != napi_pending_exception) {   \
39       JNTHROW_LAST(env);                        \
40     }                                           \
41 } while (0)
42 
43 #define JNRC(env, rc) do {                      \
44     if (rc && !jn_is_exception_pending(env)) {  \
45       JNTHROW(env, rc, 0);                      \
46     }                                           \
47 } while (0)
48 
49 #define JNRET(ns, env, call, res) do {          \
50     ns = (call);                                \
51     if (ns) {                                   \
52       if (ns != napi_pending_exception) {       \
53         JNTHROW_LAST(env);                      \
54       }                                         \
55       return (res);                             \
56     }                                           \
57 } while (0)
58 
59 #define JNGO(ns, env, call, label) do {         \
60     ns = (call);                                \
61     if (ns) {                                   \
62       if (ns != napi_pending_exception) {       \
63         JNTHROW_LAST(env);                      \
64       }                                         \
65       goto label;                               \
66     }                                           \
67 } while (0)
68 
jn_create_error(napi_env env,iwrc rc,const char * location,const char * msg)69 static napi_value jn_create_error(napi_env env, iwrc rc, const char *location, const char *msg) {
70   // Eg:
71   //  Error [@ejdb IWRC:70002 open]: IO error with expected errno status set. (IW_ERROR_IO_ERRNO)
72   char codebuf[255];
73   const char *code = codebuf;
74   if (!msg) {
75     if (rc) {
76       msg = iwlog_ecode_explained(rc);
77     } else {
78       msg = "";
79     }
80   }
81   if (rc) {
82     iwrc_strip_code(&rc);
83     if (location) {
84       snprintf(codebuf, sizeof(codebuf), "@ejdb IWRC:%" PRId64 " %s", rc, location);
85     } else {
86       snprintf(codebuf, sizeof(codebuf), "@ejdb IWRC:%" PRId64, rc);
87     }
88   } else {
89     code = "@ejdb";
90   }
91   napi_status ns;
92   napi_value vcode, vmsg, verr = 0;
93   JNGO(ns, env, napi_create_string_utf8(env, code, NAPI_AUTO_LENGTH, &vcode), finish);
94   JNGO(ns, env, napi_create_string_utf8(env, msg, NAPI_AUTO_LENGTH, &vmsg), finish);
95   JNGO(ns, env, napi_create_error(env, vcode, vmsg, &verr), finish);
96 
97 finish:
98   return verr;
99 }
100 
jn_throw_error(napi_env env,iwrc rc,const char * location,const char * msg)101 IW_INLINE bool jn_throw_error(napi_env env, iwrc rc, const char *location, const char *msg) {
102   napi_value verr = jn_create_error(env, rc, location, msg);
103   return verr && napi_throw(env, verr) == napi_ok;
104 }
105 
106 typedef enum {
107   _JN_ERROR_START = (IW_ERROR_START + 15000UL + 6000),
108   JN_ERROR_INVALID_NATIVE_CALL_ARGS, /**< Invalid native function call args (JN_ERROR_INVALID_NATIVE_CALL_ARGS) */
109   JN_ERROR_INVALID_STATE,            /**< Invalid native extension state (JN_ERROR_INVALID_STATE) */
110   JN_ERROR_QUERY_IN_USE,
111   /**< Query object is in use by active async iteration, and cannot be changed
112      (JN_ERROR_QUERY_IN_USE) */
113   JN_ERROR_NAPI,                     /*< N-API Error (JN_ERROR_NAPI) */
114   _JN_ERROR_END,
115 } jn_ecode_t;
116 
117 typedef struct JBN {
118   EJDB      db;
119   IWPOOL   *pool;
120   EJDB_OPTS opts;
121   napi_threadsafe_function resultset_tsf;
122 } *JBN;
123 
124 typedef struct JNWORK {
125   iwrc rc;        // RC error
126   napi_status     ns;
127   napi_deferred   deferred;
128   napi_async_work async_work;
129   const char     *async_resource;
130   void *unwrapped;
131   void *data;
132   void (*release_data)(napi_env env, struct JNWORK *w);
133   IWPOOL *pool;
134 } *JNWORK;
135 
136 // Globals
137 
138 static napi_ref k_vadd_streamfn_ref;
139 
jn_get_ref(napi_env env,napi_ref ref)140 IW_INLINE napi_value jn_get_ref(napi_env env, napi_ref ref) {
141   napi_value ret;
142   napi_status ns;
143   JNRET(ns, env, napi_get_reference_value(env, ref, &ret), 0);
144   return ret;
145 }
146 
jn_null(napi_env env)147 IW_INLINE napi_value jn_null(napi_env env) {
148   napi_value ret;
149   napi_status ns;
150   JNRET(ns, env, napi_get_null(env, &ret), 0);
151   return ret;
152 }
153 
jn_global(napi_env env)154 IW_INLINE napi_value jn_global(napi_env env) {
155   napi_value ret;
156   napi_status ns;
157   JNRET(ns, env, napi_get_global(env, &ret), 0);
158   return ret;
159 }
160 
jn_undefined(napi_env env)161 IW_INLINE napi_value jn_undefined(napi_env env) {
162   napi_value ret;
163   napi_status ns;
164   JNRET(ns, env, napi_get_undefined(env, &ret), 0);
165   return ret;
166 }
167 
jn_is_null(napi_env env,napi_value val)168 IW_INLINE bool jn_is_null(napi_env env, napi_value val) {
169   bool bv = false;
170   napi_value rv = jn_null(env);
171   if (!rv) {
172     return false;
173   }
174   napi_strict_equals(env, val, rv, &bv);
175   return bv;
176 }
177 
jn_is_undefined(napi_env env,napi_value val)178 IW_INLINE bool jn_is_undefined(napi_env env, napi_value val) {
179   bool bv = false;
180   napi_value rv = jn_undefined(env);
181   if (!rv) {
182     return false;
183   }
184   napi_strict_equals(env, val, rv, &bv);
185   return bv;
186 }
187 
jn_is_null_or_undefined(napi_env env,napi_value val)188 IW_INLINE bool jn_is_null_or_undefined(napi_env env, napi_value val) {
189   return jn_is_null(env, val) || jn_is_undefined(env, val);
190 }
191 
jn_create_string(napi_env env,const char * str)192 IW_INLINE napi_value jn_create_string(napi_env env, const char *str) {
193   napi_value ret;
194   napi_status ns;
195   JNRET(ns, env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &ret), 0);
196   return ret;
197 }
198 
jn_create_int64(napi_env env,int64_t val)199 IW_INLINE napi_value jn_create_int64(napi_env env, int64_t val) {
200   napi_value ret;
201   napi_status ns;
202   JNRET(ns, env, napi_create_int64(env, val, &ret), 0);
203   return ret;
204 }
205 
jn_string(napi_env env,napi_value val_,IWPOOL * pool,bool nulls,bool coerce,iwrc * rcp)206 static char *jn_string(napi_env env, napi_value val_, IWPOOL *pool, bool nulls, bool coerce, iwrc *rcp) {
207   *rcp = 0;
208   size_t len = 0;
209   napi_status ns = 0;
210   napi_value val = val_;
211   if (nulls && jn_is_null_or_undefined(env, val)) {
212     return 0;
213   }
214   if (coerce) {
215     ns = napi_coerce_to_string(env, val, &val);
216     if (ns) {
217       *rcp = JN_ERROR_NAPI;
218       JNCHECK(ns, env);
219       return 0;
220     }
221   }
222   ns = napi_get_value_string_utf8(env, val, 0, 0, &len);
223   if (ns) {
224     *rcp = JN_ERROR_NAPI;
225     JNCHECK(ns, env);
226     return 0;
227   }
228   if (!len) {
229     return 0;
230   }
231   char *buf = iwpool_alloc(len + 1, pool);
232   if (!buf) {
233     *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno);
234     return 0;
235   }
236   ns = napi_get_value_string_utf8(env, val, buf, len + 1, &len);
237   if (ns) {
238     *rcp = JN_ERROR_NAPI;
239     JNCHECK(ns, env);
240     return 0;
241   }
242   return buf;
243 }
244 
jn_string_at(napi_env env,IWPOOL * pool,napi_value arr,bool nulls,bool coerce,uint32_t idx,iwrc * rcp)245 static char *jn_string_at(
246   napi_env env, IWPOOL *pool, napi_value arr,
247   bool nulls, bool coerce, uint32_t idx, iwrc *rcp) {
248   *rcp = 0;
249   napi_value el;
250   napi_status ns = napi_get_element(env, arr, idx, &el);
251   if (ns) {
252     *rcp = JN_ERROR_NAPI;
253     JNCHECK(ns, env);
254     return 0;
255   }
256   return jn_string(env, el, pool, nulls, coerce, rcp);
257 }
258 
jn_int(napi_env env,napi_value val_,bool nulls,bool coerce,iwrc * rcp)259 static int64_t jn_int(napi_env env, napi_value val_, bool nulls, bool coerce, iwrc *rcp) {
260   *rcp = 0;
261   napi_status ns = 0;
262   napi_value val = val_;
263   if (nulls && jn_is_null_or_undefined(env, val)) {
264     return 0;
265   }
266   if (coerce) {
267     ns = napi_coerce_to_number(env, val, &val);
268     if (ns) {
269       *rcp = JN_ERROR_NAPI;
270       JNCHECK(ns, env);
271       return 0;
272     }
273   }
274   int64_t rv = 0;
275   ns = napi_get_value_int64(env, val, &rv);
276   if (ns) {
277     *rcp = JN_ERROR_NAPI;
278     JNCHECK(ns, env);
279     return 0;
280   }
281   return rv;
282 }
283 
jn_int_at(napi_env env,napi_value arr,bool nulls,bool coerce,uint32_t idx,iwrc * rcp)284 static int64_t jn_int_at(napi_env env, napi_value arr, bool nulls, bool coerce, uint32_t idx, iwrc *rcp) {
285   *rcp = 0;
286   napi_value el;
287   napi_status ns = napi_get_element(env, arr, idx, &el);
288   if (ns) {
289     *rcp = JN_ERROR_NAPI;
290     JNCHECK(ns, env);
291     return 0;
292   }
293   return jn_int(env, el, nulls, coerce, rcp);
294 }
295 
jn_double(napi_env env,napi_value val_,bool nulls,bool coerce,iwrc * rcp)296 static double jn_double(napi_env env, napi_value val_, bool nulls, bool coerce, iwrc *rcp) {
297   *rcp = 0;
298   napi_status ns = 0;
299   napi_value val = val_;
300   if (nulls && jn_is_null_or_undefined(env, val)) {
301     return 0;
302   }
303   if (coerce) {
304     ns = napi_coerce_to_number(env, val, &val);
305     if (ns) {
306       *rcp = JN_ERROR_NAPI;
307       JNCHECK(ns, env);
308       return 0;
309     }
310   }
311   double rv = 0;
312   ns = napi_get_value_double(env, val, &rv);
313   if (ns) {
314     *rcp = JN_ERROR_NAPI;
315     JNCHECK(ns, env);
316     return 0;
317   }
318   return rv;
319 }
320 
jn_bool(napi_env env,napi_value val_,bool nulls,bool coerce,iwrc * rcp)321 static bool jn_bool(napi_env env, napi_value val_, bool nulls, bool coerce, iwrc *rcp) {
322   *rcp = 0;
323   napi_status ns = 0;
324   napi_value val = val_;
325   if (nulls && jn_is_null_or_undefined(env, val)) {
326     return false;
327   }
328   if (coerce) {
329     ns = napi_coerce_to_bool(env, val, &val);
330     if (ns) {
331       *rcp = JN_ERROR_NAPI;
332       JNCHECK(ns, env);
333       return false;
334     }
335   }
336   bool rv = false;
337   ns = napi_get_value_bool(env, val, &rv);
338   if (ns) {
339     *rcp = JN_ERROR_NAPI;
340     JNCHECK(ns, env);
341     return false;
342   }
343   return rv;
344 }
345 
jn_bool_at(napi_env env,napi_value arr,bool nulls,bool coerce,uint32_t idx,iwrc * rcp)346 static bool jn_bool_at(napi_env env, napi_value arr, bool nulls, bool coerce, uint32_t idx, iwrc *rcp) {
347   *rcp = 0;
348   napi_value el;
349   napi_status ns = napi_get_element(env, arr, idx, &el);
350   if (ns) {
351     *rcp = JN_ERROR_NAPI;
352     JNCHECK(ns, env);
353     return false;
354   }
355   return jn_bool(env, el, nulls, coerce, rcp);
356 }
357 
jb_jbn_alloc(JBN * vp)358 static iwrc jb_jbn_alloc(JBN *vp) {
359   *vp = 0;
360   IWPOOL *pool = iwpool_create(255);
361   if (!pool) {
362     return iwrc_set_errno(IW_ERROR_ALLOC, errno);
363   }
364   JBN v = iwpool_calloc(sizeof(*v), pool);
365   if (!v) {
366     iwrc rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
367     iwpool_destroy(pool);
368     return rc;
369   }
370   v->pool = pool;
371   *vp = v;
372   return 0;
373 }
374 
jn_jbn_destroy(napi_env env,JBN * vp)375 static void jn_jbn_destroy(napi_env env, JBN *vp) {
376   if (!vp || !*vp) {
377     return;
378   }
379   JBN v = *vp;
380   if (v->db) {
381     iwrc rc = ejdb_close(&v->db);
382     if (rc) {
383       iwlog_ecode_error3(rc);
384     }
385     JNRC(env, rc);
386   }
387   if (v->resultset_tsf) {
388     napi_release_threadsafe_function(v->resultset_tsf, napi_tsfn_abort);
389   }
390   if (v->pool) {
391     iwpool_destroy(v->pool);
392   }
393   *vp = 0;
394 }
395 
jn_work_create(iwrc * rcp)396 static JNWORK jn_work_create(iwrc *rcp) {
397   *rcp = 0;
398   IWPOOL *pool = iwpool_create(255);
399   if (!pool) {
400     *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno);
401     return 0;
402   }
403   JNWORK w = iwpool_calloc(sizeof(*w), pool);
404   if (!w) {
405     *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno);
406     iwpool_destroy(pool);
407     return 0;
408   }
409   w->pool = pool;
410   return w;
411 }
412 
jn_work_alloc_data(size_t siz,JNWORK work,iwrc * rcp)413 static void *jn_work_alloc_data(size_t siz, JNWORK work, iwrc *rcp) {
414   *rcp = 0;
415   work->data = iwpool_calloc(siz, work->pool);
416   if (!work->data) {
417     *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno);
418   }
419   return work->data;
420 }
421 
jn_work_destroy(napi_env env,JNWORK * wp)422 void jn_work_destroy(napi_env env, JNWORK *wp) {
423   if (!wp || !*wp) {
424     return;
425   }
426   JNWORK w = *wp;
427   if (w->deferred) {
428     // Reject promise with undefined value
429     napi_reject_deferred(env, w->deferred, jn_undefined(env));
430   }
431   if (w->async_work) {
432     napi_delete_async_work(env, w->async_work);
433   }
434   if (w->release_data) {
435     w->release_data(env, w);
436   }
437   if (w->pool) {
438     iwpool_destroy(w->pool);
439   }
440   *wp = 0;
441 }
442 
jn_ejdb2impl_finalize(napi_env env,void * data,void * hint)443 static void jn_ejdb2impl_finalize(napi_env env, void *data, void *hint) {
444   JBN jbn = data;
445   jn_jbn_destroy(env, &jbn);
446 }
447 
448 // string array opts
jn_ejdb2impl_ctor(napi_env env,napi_callback_info info)449 static napi_value jn_ejdb2impl_ctor(napi_env env, napi_callback_info info) {
450   iwrc rc = 0;
451   napi_status ns = 0;
452 
453   size_t argc = 1;
454   bool bv = false;
455   uint32_t ulv = 0;
456 
457   JBN jbn = 0;
458   napi_value varr, this;
459   void *data;
460 
461   JNGO(ns, env, napi_get_cb_info(env, info, &argc, &varr, &this, &data), finish);
462   if (argc != 1) {
463     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
464     JNRC(env, rc);
465     goto finish;
466   }
467 
468   napi_is_array(env, varr, &bv);
469   if (!bv) {
470     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
471     JNRC(env, rc);
472     goto finish;
473   }
474   JNGO(ns, env, napi_get_array_length(env, varr, &ulv), finish);
475   if (ulv != 16) {
476     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
477     JNRC(env, rc);
478     goto finish;
479   }
480 
481   rc = jb_jbn_alloc(&jbn);
482   RCGO(rc, finish);
483 
484   // opts.kv.path                         // non null
485   // opts.kv.oflags                       // non null
486   // opts.kv.wal.enabled                  // non null
487   // opts.kv.wal.check_crc_on_checkpoint
488   // opts.kv.wal.checkpoint_buffer_sz
489   // opts.kv.wal.checkpoint_timeout_sec
490   // opts.kv.wal.savepoint_timeout_sec
491   // opts.kv.wal.wal_buffer_sz
492   // opts.document_buffer_sz
493   // opts.sort_buffer_sz
494   // opts.http.enabled
495   // opts.http.access_token
496   // opts.http.bind
497   // opts.http.max_body_size
498   // opts.http.port
499   // opts.http.read_anon
500 
501   argc = 0;
502   jbn->opts.kv.path = jn_string_at(env, jbn->pool, varr, false, false, argc++, &rc);
503   RCGO(rc, finish);
504   jbn->opts.kv.oflags = jn_int_at(env, varr, false, false, argc++, &rc);
505   RCGO(rc, finish);
506   jbn->opts.kv.wal.enabled = jn_bool_at(env, varr, false, false, argc++, &rc);
507   RCGO(rc, finish);
508   jbn->opts.kv.wal.check_crc_on_checkpoint = jn_bool_at(env, varr, true, true, argc++, &rc);
509   RCGO(rc, finish);
510   jbn->opts.kv.wal.checkpoint_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc);
511   RCGO(rc, finish);
512   jbn->opts.kv.wal.checkpoint_timeout_sec = jn_int_at(env, varr, true, false, argc++, &rc);
513   RCGO(rc, finish);
514   jbn->opts.kv.wal.savepoint_timeout_sec = jn_int_at(env, varr, true, false, argc++, &rc);
515   RCGO(rc, finish);
516   jbn->opts.kv.wal.wal_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc);
517   RCGO(rc, finish);
518   jbn->opts.document_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc);
519   RCGO(rc, finish);
520   jbn->opts.sort_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc);
521   RCGO(rc, finish);
522   jbn->opts.http.enabled = jn_bool_at(env, varr, true, true, argc++, &rc);
523   RCGO(rc, finish);
524   jbn->opts.http.access_token = jn_string_at(env, jbn->pool, varr, true, false, argc++, &rc);
525   RCGO(rc, finish);
526   jbn->opts.http.bind = jn_string_at(env, jbn->pool, varr, true, false, argc++, &rc);
527   RCGO(rc, finish);
528   jbn->opts.http.max_body_size = jn_int_at(env, varr, true, false, argc++, &rc);
529   RCGO(rc, finish);
530   jbn->opts.http.port = jn_int_at(env, varr, true, false, argc++, &rc);
531   RCGO(rc, finish);
532   jbn->opts.http.read_anon = jn_bool_at(env, varr, true, true, argc++, &rc);
533   RCGO(rc, finish);
534 
535   jbn->opts.kv.file_lock_fail_fast = true;
536   jbn->opts.no_wal = !jbn->opts.kv.wal.enabled;
537   jbn->opts.http.blocking = false;
538   jbn->opts.http.access_token_len = jbn->opts.http.access_token ? strlen(jbn->opts.http.access_token) : 0;
539 
540   // Result-set thread-save function initialization
541   napi_value vadd_streamfn;
542   JNGO(ns, env, napi_get_reference_value(env, k_vadd_streamfn_ref, &vadd_streamfn), finish);
543   JNGO(ns, env, napi_create_threadsafe_function(
544          env,                                           // napi_env env,
545          vadd_streamfn,                                 // napi_value func,
546          0,                                             // napi_value async_resource,
547          jn_create_string(env, "jn_add_stream_result"), // napi_value async_resource_name,
548          1,                                             // size_t max_queue_size,
549          1,                                             // size_t initial_thread_count,
550          0,                                             // void* thread_finalize_data,
551          0,                                             // napi_finalize thread_finalize_cb,
552          0,                                             // void* context,
553          jn_resultset_tsf,                              // napi_threadsafe_function_call_js call_js_cb,
554          &jbn->resultset_tsf                            // napi_threadsafe_function* result
555          ), finish);
556 
557   // Wrap class instance
558   JNGO(ns, env, napi_wrap(env, this, jbn, jn_ejdb2impl_finalize, 0, 0), finish);
559 
560 finish:
561   if (jn_is_exception_pending(env) || rc) {
562     JNRC(env, rc);
563     if (jbn) {
564       jn_jbn_destroy(env, &jbn);
565     }
566     return 0;
567   }
568   return this;
569 }
570 
571 #define JNFUNC(func)       {#func, 0, jn_ ## func, 0, 0, 0, napi_default, 0 }
572 #define JNVAL(name, value) {#name, 0, 0, 0, 0, value, napi_default, 0 }
573 
jn_resolve_pending_errors(napi_env env,napi_status ns,JNWORK work)574 bool jn_resolve_pending_errors(napi_env env, napi_status ns, JNWORK work) {
575   assert(work);
576   if (!work->deferred) {
577     return true;
578   }
579   bool pending = jn_is_exception_pending(env);
580   if (!(work->rc || ns || pending)) {
581     return false;
582   }
583   napi_value ex;
584   if (pending && (!work->rc || (work->rc == JN_ERROR_NAPI))) {
585     ns = napi_get_and_clear_last_exception(env, &ex);
586     if (ns == napi_ok) {
587       napi_reject_deferred(env, work->deferred, ex);
588     }
589   } else {
590     napi_value verr;
591     const napi_extended_error_info *info = 0;
592     if (ns) {
593       napi_get_last_error_info(env, &info);
594     }
595     if (pending) {
596       napi_get_and_clear_last_exception(env, &ex);
597     }
598     verr = jn_create_error(env, work->rc, work->async_resource, info ? info->error_message : 0);
599     if (verr) {
600       napi_reject_deferred(env, work->deferred, verr);
601     }
602   }
603   work->deferred = 0;
604   return true;
605 }
606 
jn_launch_promise(napi_env env,napi_callback_info info,const char * async_resource_name,napi_async_execute_callback execute,napi_async_complete_callback complete,JNWORK work)607 static napi_value jn_launch_promise(
608   napi_env                     env,
609   napi_callback_info           info,
610   const char                  *async_resource_name,
611   napi_async_execute_callback  execute,
612   napi_async_complete_callback complete,
613   JNWORK                       work) {
614   size_t argc = 0;
615   napi_status ns = 0;
616   napi_value promise = 0, this, awork;
617   void *data;
618 
619   work->async_resource = async_resource_name;
620   JNGO(ns, env, napi_get_cb_info(env, info, &argc, 0, &this, &data), finish);
621   JNGO(ns, env, napi_unwrap(env, this, &work->unwrapped), finish);
622   JNGO(ns, env, napi_create_promise(env, &work->deferred, &promise), finish);
623   JNGO(ns, env, napi_create_string_utf8(env, async_resource_name, NAPI_AUTO_LENGTH, &awork), finish);
624   JNGO(ns, env, napi_create_async_work(env, 0, awork, execute, complete, work, &work->async_work), finish);
625   JNGO(ns, env, napi_queue_async_work(env, work->async_work), finish);
626 
627 finish:
628   if (ns || jn_is_exception_pending(env)) {
629     if (work) {
630       jn_resolve_pending_errors(env, ns, work);
631       jn_work_destroy(env, &work);
632     }
633   }
634   return promise;
635 }
636 
637 //  ---------------- EJDB2.open()
638 
jn_open_execute(napi_env env,void * data)639 static void jn_open_execute(napi_env env, void *data) {
640   JNWORK work = data;
641   JBN jbn = work->unwrapped;
642   if (jbn->db) {
643     return;            // Database is already opened
644   }
645   work->rc = ejdb_open(&jbn->opts, &jbn->db);
646 }
647 
jn_open_complete(napi_env env,napi_status ns,void * data)648 static void jn_open_complete(napi_env env, napi_status ns, void *data) {
649   JNWORK work = data;
650   if (jn_resolve_pending_errors(env, ns, work)) {
651     goto finish;
652   }
653   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
654   work->deferred = 0;
655 
656 finish:
657   jn_work_destroy(env, &work);
658 }
659 
jn_open(napi_env env,napi_callback_info info)660 static napi_value jn_open(napi_env env, napi_callback_info info) {
661   iwrc rc;
662   JNWORK work = jn_work_create(&rc);
663   if (rc) {
664     JNRC(env, rc);
665     return jn_undefined(env);
666   }
667   napi_value ret = jn_launch_promise(env, info, "open", jn_open_execute, jn_open_complete, work);
668   return ret ? ret : jn_undefined(env);
669 }
670 
671 //  ---------------- EJDB2.close()
672 
jn_close_execute(napi_env env,void * data)673 static void jn_close_execute(napi_env env, void *data) {
674   JNWORK work = data;
675   JBN jbn = work->unwrapped;
676   if (!jbn->db) {
677     return;
678   }
679   work->rc = ejdb_close(&jbn->db);
680 }
681 
jn_close_complete(napi_env env,napi_status ns,void * data)682 static void jn_close_complete(napi_env env, napi_status ns, void *data) {
683   JNWORK work = data;
684   JBN jbn = work->unwrapped;
685   if (jn_resolve_pending_errors(env, ns, work)) {
686     goto finish;
687   }
688   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
689   work->deferred = 0;
690 
691 finish:
692   if (jbn->resultset_tsf) {
693     napi_release_threadsafe_function(jbn->resultset_tsf, napi_tsfn_abort);
694     jbn->resultset_tsf = 0;
695   }
696   jn_work_destroy(env, &work);
697 }
698 
jn_close(napi_env env,napi_callback_info info)699 static napi_value jn_close(napi_env env, napi_callback_info info) {
700   iwrc rc;
701   JNWORK work = jn_work_create(&rc);
702   if (rc) {
703     JNRC(env, rc);
704     return jn_undefined(env);
705   }
706   napi_value ret = jn_launch_promise(env, info, "close", jn_close_execute, jn_close_complete, work);
707   return ret ? ret : jn_undefined(env);
708 }
709 
710 //  ---------------- EJDB2.put/patch()
711 
712 struct JNPUT_DATA {
713   int64_t     id;
714   const char *coll;
715   const char *json;
716   bool patch;
717 };
718 
jn_put_execute(napi_env env,void * data)719 static void jn_put_execute(napi_env env, void *data) {
720   JBL jbl = 0;
721   JNWORK work = data;
722   JBN jbn = work->unwrapped;
723   if (!jbn->db) {
724     work->rc = JN_ERROR_INVALID_STATE;
725     goto finish;
726   }
727   struct JNPUT_DATA *wdata = work->data;
728   if (!wdata->patch) {
729     work->rc = jbl_from_json(&jbl, wdata->json);
730     RCGO(work->rc, finish);
731   }
732   if (wdata->id > 0) {
733     if (wdata->patch) {
734       work->rc = ejdb_patch(jbn->db, wdata->coll, wdata->json, wdata->id);
735     } else {
736       work->rc = ejdb_put(jbn->db, wdata->coll, jbl, wdata->id);
737     }
738   } else {
739     work->rc = ejdb_put_new(jbn->db, wdata->coll, jbl, &wdata->id);
740   }
741 finish:
742   if (jbl) {
743     jbl_destroy(&jbl);
744   }
745 }
746 
jn_put_complete(napi_env env,napi_status ns,void * data)747 static void jn_put_complete(napi_env env, napi_status ns, void *data) {
748   napi_value rv;
749   JNWORK work = data;
750   if (jn_resolve_pending_errors(env, ns, work)) {
751     goto finish;
752   }
753   struct JNPUT_DATA *wdata = work->data;
754   ns = napi_create_int64(env, wdata->id, &rv);
755   if (ns) {
756     jn_resolve_pending_errors(env, ns, work);
757     goto finish;
758   }
759   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish);
760   work->deferred = 0;
761 
762 finish:
763   jn_work_destroy(env, &work);
764 }
765 
766 // collection, json, id
jn_put_patch(napi_env env,napi_callback_info info,bool patch,bool upsert)767 static napi_value jn_put_patch(napi_env env, napi_callback_info info, bool patch, bool upsert) {
768   iwrc rc = 0;
769   napi_status ns = 0;
770   napi_value this, argv[3] = { 0 };
771   size_t argc = sizeof(argv) / sizeof(argv[0]);
772   void *data;
773   napi_value ret = 0;
774   JNWORK work = jn_work_create(&rc);
775   RCGO(rc, finish);
776 
777   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
778   if (argc != sizeof(argv) / sizeof(argv[0])) {
779     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
780     goto finish;
781   }
782 
783   struct JNPUT_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
784   RCGO(rc, finish);
785   wdata->patch = patch;
786   wdata->coll = jn_string(env, argv[0], work->pool, false, true, &rc);
787   RCGO(rc, finish);
788   wdata->json = jn_string(env, argv[1], work->pool, false, false, &rc);
789   RCGO(rc, finish);
790   wdata->id = jn_int(env, argv[2], true, true, &rc);
791   RCGO(rc, finish);
792 
793   if ((wdata->id < 1) && wdata->patch) {
794     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
795     goto finish;
796   }
797   ret = jn_launch_promise(env, info, wdata->patch ? "patch" : "put", jn_put_execute, jn_put_complete, work);
798 
799 finish:
800   if (rc) {
801     JNRC(env, rc);
802     if (work) {
803       jn_work_destroy(env, &work);
804     }
805   }
806   return ret ? ret : jn_undefined(env);
807 }
808 
jn_put(napi_env env,napi_callback_info info)809 static napi_value jn_put(napi_env env, napi_callback_info info) {
810   return jn_put_patch(env, info, false, false);
811 }
812 
jn_patch(napi_env env,napi_callback_info info)813 static napi_value jn_patch(napi_env env, napi_callback_info info) {
814   return jn_put_patch(env, info, true, false);
815 }
816 
jn_patch_or_put(napi_env env,napi_callback_info info)817 static napi_value jn_patch_or_put(napi_env env, napi_callback_info info) {
818   return jn_put_patch(env, info, true, true);
819 }
820 
821 //  ---------------- EJDB2.get()
822 
823 struct JNGET_DATA {
824   int64_t     id;
825   const char *coll;
826   JBL jbl;
827 };
828 
jn_get_data_destroy(napi_env env,JNWORK w)829 static void jn_get_data_destroy(napi_env env, JNWORK w) {
830   struct JNGET_DATA *wdata = w->data;
831   if (wdata && wdata->jbl) {
832     jbl_destroy(&wdata->jbl);
833   }
834 }
835 
jn_get_execute(napi_env env,void * data)836 static void jn_get_execute(napi_env env, void *data) {
837   JNWORK work = data;
838   JBN jbn = work->unwrapped;
839   if (!jbn->db) {
840     work->rc = JN_ERROR_INVALID_STATE;
841     return;
842   }
843   struct JNGET_DATA *wdata = work->data;
844   work->rc = ejdb_get(jbn->db, wdata->coll, wdata->id, &wdata->jbl);
845 }
846 
jn_get_complete(napi_env env,napi_status ns,void * data)847 static void jn_get_complete(napi_env env, napi_status ns, void *data) {
848   napi_value rv;
849   JNWORK work = data;
850   if (jn_resolve_pending_errors(env, ns, work)) {
851     goto finish;
852   }
853   struct JNGET_DATA *wdata = work->data;
854   IWXSTR *xstr = iwxstr_new2(jbl_size(wdata->jbl) * 2);
855   if (!xstr) {
856     work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
857     goto finish0;
858   }
859   work->rc = jbl_as_json(wdata->jbl, jbl_xstr_json_printer, xstr, 0);
860   RCGO(work->rc, finish0);
861   JNGO(ns, env, napi_create_string_utf8(env, iwxstr_ptr(xstr), iwxstr_size(xstr), &rv), finish0);
862   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish0);
863   work->deferred = 0;
864 
865 finish0:
866   if (xstr) {
867     iwxstr_destroy(xstr);
868   }
869   if (work->rc || ns) {
870     jn_resolve_pending_errors(env, ns, work);
871   }
872 finish:
873   jn_work_destroy(env, &work);
874 }
875 
jn_get(napi_env env,napi_callback_info info)876 static napi_value jn_get(napi_env env, napi_callback_info info) {
877   iwrc rc = 0;
878   napi_status ns;
879   napi_value this, argv[2];
880   napi_value ret = 0;
881   size_t argc = sizeof(argv) / sizeof(argv[0]);
882   void *data;
883 
884   JNWORK work = jn_work_create(&rc);
885   RCGO(rc, finish);
886 
887   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
888   if (argc != sizeof(argv) / sizeof(argv[0])) {
889     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
890     goto finish;
891   }
892 
893   struct JNGET_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
894   RCGO(rc, finish);
895   work->release_data = jn_get_data_destroy;
896   wdata->coll = jn_string(env, argv[0], work->pool, false, false, &rc);
897   RCGO(rc, finish);
898   wdata->id = jn_int(env, argv[1], false, false, &rc);
899   RCGO(rc, finish);
900 
901   ret = jn_launch_promise(env, info, "get", jn_get_execute, jn_get_complete, work);
902 
903 finish:
904   if (rc) {
905     JNRC(env, rc);
906     if (work) {
907       jn_work_destroy(env, &work);
908     }
909   }
910   return ret ? ret : jn_undefined(env);
911 }
912 
913 //  ---------------- EJDB2.del()
914 
jn_del_execute(napi_env env,void * data)915 static void jn_del_execute(napi_env env, void *data) {
916   JNWORK work = data;
917   JBN jbn = work->unwrapped;
918   if (!jbn->db) {
919     work->rc = JN_ERROR_INVALID_STATE;
920     return;
921   }
922   struct JNGET_DATA *wdata = work->data;
923   work->rc = ejdb_del(jbn->db, wdata->coll, wdata->id);
924 }
925 
jn_del_complete(napi_env env,napi_status ns,void * data)926 static void jn_del_complete(napi_env env, napi_status ns, void *data) {
927   JNWORK work = data;
928   if (jn_resolve_pending_errors(env, ns, work)) {
929     goto finish;
930   }
931   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
932   work->deferred = 0;
933 
934 finish:
935   jn_work_destroy(env, &work);
936 }
937 
jn_del(napi_env env,napi_callback_info info)938 static napi_value jn_del(napi_env env, napi_callback_info info) {
939   iwrc rc = 0;
940   napi_status ns;
941   napi_value this, argv[2];
942   napi_value ret = 0;
943   size_t argc = sizeof(argv) / sizeof(argv[0]);
944   void *data;
945 
946   JNWORK work = jn_work_create(&rc);
947   RCGO(rc, finish);
948 
949   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
950   if (argc != sizeof(argv) / sizeof(argv[0])) {
951     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
952     goto finish;
953   }
954 
955   struct JNGET_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
956   RCGO(rc, finish);
957   wdata->coll = jn_string(env, argv[0], work->pool, false, false, &rc);
958   RCGO(rc, finish);
959   wdata->id = jn_int(env, argv[1], false, false, &rc);
960   RCGO(rc, finish);
961 
962   ret = jn_launch_promise(env, info, "del", jn_del_execute, jn_del_complete, work);
963 
964 finish:
965   if (rc) {
966     JNRC(env, rc);
967     if (work) {
968       jn_work_destroy(env, &work);
969     }
970   }
971   return ret ? ret : jn_undefined(env);
972 }
973 
974 //  ---------------- EJDB2.renameCollection()
975 
976 struct JNRENAME_DATA {
977   const char *old_name;
978   const char *new_name;
979 };
980 
jn_rename_collection_execute(napi_env env,void * data)981 static void jn_rename_collection_execute(napi_env env, void *data) {
982   JNWORK work = data;
983   JBN jbn = work->unwrapped;
984   if (!jbn->db) {
985     work->rc = JN_ERROR_INVALID_STATE;
986     return;
987   }
988   struct JNRENAME_DATA *wdata = work->data;
989   work->rc = ejdb_rename_collection(jbn->db, wdata->old_name, wdata->new_name);
990 }
991 
jn_rename_collection_complete(napi_env env,napi_status ns,void * data)992 static void jn_rename_collection_complete(napi_env env, napi_status ns, void *data) {
993   JNWORK work = data;
994   if (jn_resolve_pending_errors(env, ns, work)) {
995     goto finish;
996   }
997   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
998   work->deferred = 0;
999 
1000 finish:
1001   jn_work_destroy(env, &work);
1002 }
1003 
jn_rename_collection(napi_env env,napi_callback_info info)1004 static napi_value jn_rename_collection(napi_env env, napi_callback_info info) {
1005   iwrc rc = 0;
1006   napi_status ns;
1007   napi_value this, argv[2];
1008   napi_value ret = 0;
1009   size_t argc = sizeof(argv) / sizeof(argv[0]);
1010   void *data;
1011 
1012   JNWORK work = jn_work_create(&rc);
1013   RCGO(rc, finish);
1014 
1015   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
1016   if (argc != sizeof(argv) / sizeof(argv[0])) {
1017     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1018     goto finish;
1019   }
1020 
1021   struct JNRENAME_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
1022   RCGO(rc, finish);
1023 
1024   wdata->old_name = jn_string(env, argv[0], work->pool, false, false, &rc);
1025   RCGO(rc, finish);
1026 
1027   wdata->new_name = jn_string(env, argv[1], work->pool, false, false, &rc);
1028   RCGO(rc, finish);
1029 
1030   ret = jn_launch_promise(env, info, "rename",
1031                           jn_rename_collection_execute, jn_rename_collection_complete,
1032                           work);
1033 finish:
1034   if (rc) {
1035     JNRC(env, rc);
1036     if (work) {
1037       jn_work_destroy(env, &work);
1038     }
1039   }
1040   return ret ? ret : jn_undefined(env);
1041 }
1042 
1043 //  ---------------- EJDB2.info()
1044 
jn_info_execute(napi_env env,void * data)1045 static void jn_info_execute(napi_env env, void *data) {
1046   JNWORK work = data;
1047   JBN jbn = work->unwrapped;
1048   if (!jbn->db) {
1049     work->rc = JN_ERROR_INVALID_STATE;
1050     return;
1051   }
1052   struct JNGET_DATA *wdata = work->data;
1053   work->rc = ejdb_get_meta(jbn->db, &wdata->jbl);
1054 }
1055 
jn_info_complete(napi_env env,napi_status ns,void * data)1056 static void jn_info_complete(napi_env env, napi_status ns, void *data) {
1057   napi_value rv;
1058   JNWORK work = data;
1059   if (jn_resolve_pending_errors(env, ns, work)) {
1060     goto finish;
1061   }
1062   struct JNGET_DATA *wdata = work->data;
1063   IWXSTR *xstr = iwxstr_new2(jbl_size(wdata->jbl) * 2);
1064   if (!xstr) {
1065     work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1066     goto finish0;
1067   }
1068   work->rc = jbl_as_json(wdata->jbl, jbl_xstr_json_printer, xstr, 0);
1069   RCGO(work->rc, finish0);
1070   JNGO(ns, env, napi_create_string_utf8(env, iwxstr_ptr(xstr), iwxstr_size(xstr), &rv), finish0);
1071 
1072   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish0);
1073   work->deferred = 0;
1074 
1075 finish0:
1076   if (xstr) {
1077     iwxstr_destroy(xstr);
1078   }
1079   if (work->rc || ns) {
1080     jn_resolve_pending_errors(env, ns, work);
1081   }
1082 finish:
1083   jn_work_destroy(env, &work);
1084 }
1085 
jn_info(napi_env env,napi_callback_info info)1086 static napi_value jn_info(napi_env env, napi_callback_info info) {
1087   iwrc rc = 0;
1088   napi_status ns;
1089   napi_value this;
1090   napi_value ret = 0;
1091   size_t argc = 0;
1092   void *data;
1093   JNWORK work = jn_work_create(&rc);
1094   RCGO(rc, finish);
1095 
1096   JNGO(ns, env, napi_get_cb_info(env, info, &argc, 0, &this, &data), finish);
1097   jn_work_alloc_data(sizeof(struct JNGET_DATA), work, &rc);
1098   RCGO(rc, finish);
1099   work->release_data = jn_get_data_destroy;
1100   ret = jn_launch_promise(env, info, "info", jn_info_execute, jn_info_complete, work);
1101 
1102 finish:
1103   if (rc) {
1104     JNRC(env, rc);
1105     if (work) {
1106       jn_work_destroy(env, &work);
1107     }
1108   }
1109   return ret ? ret : jn_undefined(env);
1110 }
1111 
1112 //  ---------------- EJDB2.ensureIndex/removeIndex()
1113 
1114 struct JNIDX_DATA {
1115   const char     *coll;
1116   const char     *path;
1117   ejdb_idx_mode_t mode;
1118   bool remove;
1119 };
1120 
jn_index_execute(napi_env env,void * data)1121 static void jn_index_execute(napi_env env, void *data) {
1122   JNWORK work = data;
1123   JBN jbn = work->unwrapped;
1124   if (!jbn->db) {
1125     work->rc = JN_ERROR_INVALID_STATE;
1126     return;
1127   }
1128   struct JNIDX_DATA *wdata = work->data;
1129   if (wdata->remove) {
1130     work->rc = ejdb_remove_index(jbn->db, wdata->coll, wdata->path, wdata->mode);
1131   } else {
1132     work->rc = ejdb_ensure_index(jbn->db, wdata->coll, wdata->path, wdata->mode);
1133   }
1134 }
1135 
jn_index_complete(napi_env env,napi_status ns,void * data)1136 static void jn_index_complete(napi_env env, napi_status ns, void *data) {
1137   JNWORK work = data;
1138   if (jn_resolve_pending_errors(env, ns, work)) {
1139     goto finish;
1140   }
1141   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
1142   work->deferred = 0;
1143 
1144 finish:
1145   jn_work_destroy(env, &work);
1146 }
1147 
jn_index(napi_env env,napi_callback_info info)1148 static napi_value jn_index(napi_env env, napi_callback_info info) {
1149   iwrc rc = 0;
1150   napi_value ret = 0;
1151   napi_status ns;
1152   napi_value this, argv[4];
1153   size_t argc = sizeof(argv) / sizeof(argv[0]);
1154   void *data;
1155 
1156   JNWORK work = jn_work_create(&rc);
1157   RCGO(rc, finish);
1158 
1159   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
1160   if (argc != sizeof(argv) / sizeof(argv[0])) {
1161     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1162     goto finish;
1163   }
1164 
1165   struct JNIDX_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
1166   RCGO(rc, finish);
1167   wdata->coll = jn_string(env, argv[0], work->pool, false, false, &rc);
1168   RCGO(rc, finish);
1169   wdata->path = jn_string(env, argv[1], work->pool, false, false, &rc);
1170   RCGO(rc, finish);
1171   wdata->mode = jn_int(env, argv[2], false, false, &rc);
1172   RCGO(rc, finish);
1173   wdata->remove = jn_bool(env, argv[3], false, false, &rc);
1174   RCGO(rc, finish);
1175 
1176   ret = jn_launch_promise(env, info, "index", jn_index_execute, jn_index_complete, work);
1177 
1178 finish:
1179   if (rc) {
1180     JNRC(env, rc);
1181     if (work) {
1182       jn_work_destroy(env, &work);
1183     }
1184   }
1185   return ret ? ret : jn_undefined(env);
1186 }
1187 
1188 // ---------------- EJDB2.removeCollection()
1189 
1190 struct JNRMC_DATA {
1191   const char *coll;
1192 };
1193 
jn_rmcoll_execute(napi_env env,void * data)1194 static void jn_rmcoll_execute(napi_env env, void *data) {
1195   JNWORK work = data;
1196   JBN jbn = work->unwrapped;
1197   if (!jbn->db) {
1198     work->rc = JN_ERROR_INVALID_STATE;
1199     return;
1200   }
1201   struct JNRMC_DATA *wdata = work->data;
1202   work->rc = ejdb_remove_collection(jbn->db, wdata->coll);
1203 }
1204 
jn_rmcoll_complete(napi_env env,napi_status ns,void * data)1205 static void jn_rmcoll_complete(napi_env env, napi_status ns, void *data) {
1206   JNWORK work = data;
1207   if (jn_resolve_pending_errors(env, ns, work)) {
1208     goto finish;
1209   }
1210   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
1211   work->deferred = 0;
1212 
1213 finish:
1214   jn_work_destroy(env, &work);
1215 }
1216 
jn_rmcoll(napi_env env,napi_callback_info info)1217 static napi_value jn_rmcoll(napi_env env, napi_callback_info info) {
1218   iwrc rc = 0;
1219   napi_status ns;
1220   napi_value this, argv;
1221   napi_value ret = 0;
1222   size_t argc = 1;
1223   void *data;
1224 
1225   JNWORK work = jn_work_create(&rc);
1226   RCGO(rc, finish);
1227 
1228   JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish);
1229   if (argc != 1) {
1230     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1231     goto finish;
1232   }
1233 
1234   struct JNRMC_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
1235   RCGO(rc, finish);
1236   wdata->coll = jn_string(env, argv, work->pool, false, false, &rc);
1237   RCGO(rc, finish);
1238 
1239   ret = jn_launch_promise(env, info, "rmcoll", jn_rmcoll_execute, jn_rmcoll_complete, work);
1240 
1241 finish:
1242   if (rc) {
1243     JNRC(env, rc);
1244     if (work) {
1245       jn_work_destroy(env, &work);
1246     }
1247   }
1248   return ret ? ret : jn_undefined(env);
1249 }
1250 
1251 //  ---------------- EJDB2.onlineBackup
1252 
1253 struct JNBK_DATA {
1254   uint64_t    ts;
1255   const char *file_name;
1256 };
1257 
jn_online_backup_execute(napi_env env,void * data)1258 static void jn_online_backup_execute(napi_env env, void *data) {
1259   JNWORK work = data;
1260   JBN jbn = work->unwrapped;
1261   if (!jbn->db) {
1262     work->rc = JN_ERROR_INVALID_STATE;
1263     return;
1264   }
1265   struct JNBK_DATA *wdata = work->data;
1266   work->rc = ejdb_online_backup(jbn->db, &wdata->ts, wdata->file_name);
1267 }
1268 
jn_online_backup_complete(napi_env env,napi_status ns,void * data)1269 static void jn_online_backup_complete(napi_env env, napi_status ns, void *data) {
1270   napi_value rv;
1271   JNWORK work = data;
1272   if (jn_resolve_pending_errors(env, ns, work)) {
1273     goto finish;
1274   }
1275   struct JNBK_DATA *wdata = work->data;
1276   ns = napi_create_int64(env, (int64_t) wdata->ts, &rv);
1277   if (ns) {
1278     jn_resolve_pending_errors(env, ns, work);
1279     goto finish;
1280   }
1281   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish);
1282   work->deferred = 0;
1283 
1284 finish:
1285   jn_work_destroy(env, &work);
1286 }
1287 
jn_online_backup(napi_env env,napi_callback_info info)1288 static napi_value jn_online_backup(napi_env env, napi_callback_info info) {
1289   iwrc rc = 0;
1290   napi_status ns = 0;
1291   napi_value this, argv[1] = { 0 };
1292   size_t argc = sizeof(argv) / sizeof(argv[0]);
1293   void *data;
1294   napi_value ret = 0;
1295   JNWORK work = jn_work_create(&rc);
1296   RCGO(rc, finish);
1297 
1298   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
1299   if (argc != sizeof(argv) / sizeof(argv[0])) {
1300     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1301     goto finish;
1302   }
1303 
1304   struct JNBK_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc);
1305   RCGO(rc, finish);
1306   wdata->file_name = jn_string(env, argv[0], work->pool, false, true, &rc);
1307   ret = jn_launch_promise(env, info, "online_backup", jn_online_backup_execute, jn_online_backup_complete, work);
1308 
1309 finish:
1310   if (rc) {
1311     JNRC(env, rc);
1312     if (work) {
1313       jn_work_destroy(env, &work);
1314     }
1315   }
1316   return ret ? ret : jn_undefined(env);
1317 }
1318 
1319 // ---------------- jql_init
1320 
1321 typedef struct JNQL {
1322   int refs;
1323   JQL jql;
1324 } *JNQL;
1325 
jn_jnql_destroy_mt(JNQL * qlp)1326 static void jn_jnql_destroy_mt(JNQL *qlp) {
1327   if (!qlp || !*qlp) {
1328     return;
1329   }
1330   JNQL ql = *qlp;
1331   if (ql->jql) {
1332     jql_destroy(&ql->jql);
1333   }
1334   free(ql);
1335 }
1336 
jn_jql_finalize(napi_env env,void * data,void * hint)1337 static void jn_jql_finalize(napi_env env, void *data, void *hint) {
1338   JNQL jnql = data;
1339   if (jnql) {
1340     jn_jnql_destroy_mt(&jnql);
1341   }
1342 }
1343 
1344 // JQL._impl.jql_init(this, query, collection);
jn_jql_init(napi_env env,napi_callback_info info)1345 static napi_value jn_jql_init(napi_env env, napi_callback_info info) {
1346   iwrc rc = 0;
1347   napi_status ns;
1348   napi_value argv[3], this;
1349   size_t argc = sizeof(argv) / sizeof(argv[0]);
1350   void *data;
1351   JNQL jnql = 0;
1352 
1353   IWPOOL *pool = iwpool_create(255);
1354   if (!pool) {
1355     rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1356     goto finish;
1357   }
1358   jnql = calloc(1, sizeof(*jnql));
1359   if (!jnql) {
1360     rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1361     goto finish;
1362   }
1363 
1364   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
1365   if (argc != sizeof(argv) / sizeof(argv[0])) {
1366     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1367     goto finish;
1368   }
1369 
1370   const char *query = jn_string(env, argv[1], pool, false, false, &rc);
1371   RCGO(rc, finish);
1372 
1373   const char *collection = jn_string(env, argv[2], pool, true, false, &rc);
1374   RCGO(rc, finish);
1375 
1376   rc = jql_create2(&jnql->jql, collection, query, JQL_KEEP_QUERY_ON_PARSE_ERROR | JQL_SILENT_ON_PARSE_ERROR);
1377   RCGO(rc, finish);
1378 
1379   JNGO(ns, env, napi_wrap(env, argv[0], jnql, jn_jql_finalize, 0, 0), finish);
1380 
1381   napi_value vcoll = jn_create_string(env, jql_collection(jnql->jql));
1382   if (!vcoll) {
1383     goto finish;
1384   }
1385   JNGO(ns, env, napi_set_named_property(env, argv[0], "collection", vcoll), finish);
1386 
1387 finish:
1388   if (rc) {
1389     if ((rc == JQL_ERROR_QUERY_PARSE) && jnql && jnql->jql) {
1390       JNTHROW(env, rc, jql_error(jnql->jql));
1391     } else {
1392       JNRC(env, rc);
1393     }
1394     if (jnql) {
1395       jn_jnql_destroy_mt(&jnql);
1396     }
1397   }
1398   iwpool_destroy(pool);
1399   return jn_undefined(env);
1400 }
1401 
1402 // ---------------- jql_stream_attach
1403 
1404 typedef struct JNQS { // query stream associated data
1405   volatile bool aborted;
1406   volatile bool paused;
1407   int      refs;
1408   JBN      jbn;
1409   JNQL     jnql;
1410   napi_ref stream_ref;      // Reference to the stream object
1411   napi_ref explain_cb_ref;  // Reference to the optional explain callback
1412   int64_t  limit;
1413   struct JNWORK   work;
1414   pthread_mutex_t mtx;
1415   pthread_cond_t  cond;
1416 } *JNQS;
1417 
1418 typedef struct JNCS { // call data to `k_add_stream_tsfn`
1419   bool     has_count;
1420   IWXSTR  *log;
1421   IWXSTR  *document;
1422   int64_t  count;
1423   int64_t  document_id;
1424   napi_ref stream_ref; // copied from `JNQS`
1425 } *JNCS;
1426 
jn_cs_destroy(JNCS * csp)1427 static void jn_cs_destroy(JNCS *csp) {
1428   if (!csp || !*csp) {
1429     return;
1430   }
1431   JNCS cs = *csp;
1432   if (cs->document) {
1433     iwxstr_destroy(cs->document);
1434   }
1435   if (cs->log) {
1436     iwxstr_destroy(cs->log);
1437   }
1438   free(cs);
1439   *csp = 0;
1440 }
1441 
1442 // function addStreamResult(stream, id, jsondoc, log)
jn_resultset_tsf(napi_env env,napi_value js_add_stream,void * context,void * data)1443 static void jn_resultset_tsf(
1444   napi_env   env,
1445   napi_value js_add_stream,
1446   void      *context,
1447   void      *data) {
1448 
1449   if (!env) { // shutdown pending
1450     JNCS cs = data;
1451     jn_cs_destroy(&cs);
1452     return;
1453   }
1454 
1455   JNCS cs = data;
1456   napi_status ns;
1457   napi_value vstream, vid, vdoc, vlog, vresult;
1458   napi_value vglobal = jn_global(env);
1459   napi_value vnull = jn_null(env);
1460 
1461   if (!vglobal || !vnull) {
1462     goto finish;
1463   }
1464   vstream = jn_get_ref(env, cs->stream_ref);
1465   if (!vstream) { // exception pending
1466     goto finish;
1467   }
1468   if (cs->document) {
1469     vdoc = jn_create_string(env, iwxstr_ptr(cs->document));
1470     iwxstr_destroy(cs->document);
1471     cs->document = 0;
1472   } else if (cs->has_count) {
1473     vdoc = jn_create_int64(env, cs->count);
1474   } else {
1475     vdoc = vnull;
1476   }
1477   if (!vdoc) {
1478     goto finish;
1479   }
1480   vid = jn_create_int64(env, cs->document_id);
1481   if (!vid) {
1482     goto finish;
1483   }
1484   if (cs->log) {
1485     vlog = jn_create_string(env, iwxstr_ptr(cs->log));
1486   } else {
1487     vlog = vnull;
1488   }
1489 
1490   napi_value argv[] = { vstream, vid, vdoc, vlog };
1491   const int argc = sizeof(argv) / sizeof(argv[0]);
1492   JNGO(ns, env, napi_call_function(
1493          env,
1494          vglobal,
1495          js_add_stream,
1496          argc,
1497          argv,
1498          &vresult
1499          ), finish);
1500 
1501 finish:
1502   if (cs->document_id < 0) {
1503     uint32_t refs;
1504     napi_reference_unref(env, cs->stream_ref, &refs);
1505   }
1506   jn_cs_destroy(&cs);
1507 }
1508 
jn_jnqs_destroy_mt(napi_env env,JNQS * qsp)1509 static void jn_jnqs_destroy_mt(napi_env env, JNQS *qsp) {
1510   if (!qsp || !*qsp) {
1511     return;
1512   }
1513   JNQS qs = *qsp;
1514   uint32_t rcnt = 0;
1515   if (--qs->refs > 0) {
1516     return;
1517   }
1518   if (qs->jnql) {
1519     qs->jnql->refs--;
1520   }
1521   if (qs->stream_ref) {
1522     napi_reference_unref(env, qs->stream_ref, &rcnt);
1523     if (!rcnt) {
1524       napi_ref ref = qs->stream_ref;
1525       qs->stream_ref = 0;
1526       napi_delete_reference(env, ref);
1527     }
1528   }
1529   if (qs->explain_cb_ref) {
1530     napi_reference_unref(env, qs->explain_cb_ref, &rcnt);
1531     if (!rcnt) {
1532       napi_ref ref = qs->explain_cb_ref;
1533       qs->explain_cb_ref = 0;
1534       napi_delete_reference(env, ref);
1535     }
1536   }
1537   free(qs);
1538 }
1539 
jn_stream_pause_guard(JNQS qs)1540 static iwrc jn_stream_pause_guard(JNQS qs) {
1541   iwrc rc = 0;
1542   int rci = pthread_mutex_lock(&qs->mtx);
1543   if (rci) {
1544     return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci);
1545   }
1546   while (qs->paused) {
1547     rci = pthread_cond_wait(&qs->cond, &qs->mtx);
1548     if (rci) {
1549       rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci);
1550       break;
1551     }
1552   }
1553   pthread_mutex_unlock(&qs->mtx);
1554   return rc;
1555 }
1556 
jn_jql_stream_visitor(EJDB_EXEC * ux,EJDB_DOC doc,int64_t * step)1557 static iwrc jn_jql_stream_visitor(EJDB_EXEC *ux, EJDB_DOC doc, int64_t *step) {
1558   JNCS cs = 0;
1559   JNQS qs = ux->opaque;
1560   JNWORK work = &qs->work;
1561 
1562   if (qs->aborted) {
1563     *step = 0;
1564     return 0;
1565   }
1566   work->rc = jn_stream_pause_guard(qs);
1567   RCRET(work->rc);
1568 
1569   IWXSTR *xstr = iwxstr_new();
1570   if (!xstr) {
1571     work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1572     return work->rc;
1573   }
1574   if (doc->node) {
1575     work->rc = jbn_as_json(doc->node, jbl_xstr_json_printer, xstr, 0);
1576   } else {
1577     work->rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0);
1578   }
1579   RCGO(work->rc, finish);
1580 
1581   cs = malloc(sizeof(*cs));
1582   if (!cs) {
1583     work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1584     goto finish;
1585   }
1586   cs->count = 0;
1587   cs->has_count = false;
1588   cs->stream_ref = qs->stream_ref;
1589   cs->log = ux->log;
1590   cs->document_id = doc->id;
1591   cs->document = xstr;
1592 
1593   napi_status ns = napi_call_threadsafe_function(qs->jbn->resultset_tsf, cs, napi_tsfn_blocking);
1594   if (ns) {
1595     work->rc = JN_ERROR_NAPI;
1596     work->ns = ns;
1597     goto finish;
1598   }
1599   ux->log = 0;
1600 
1601 finish:
1602   if (work->rc) {
1603     iwxstr_destroy(xstr);
1604     if (cs) {
1605       cs->document = 0; // kept in xstr
1606       jn_cs_destroy(&cs);
1607     }
1608   }
1609   return work->rc;
1610 }
1611 
jn_jql_stream_execute(napi_env env,void * data)1612 static void jn_jql_stream_execute(napi_env env, void *data) {
1613   napi_status ns;
1614   uint32_t refs = 0;
1615   JNWORK work = data;
1616   JNQS qs = work->data;
1617   JQL q = qs->jnql->jql;
1618   JNCS cs = 0;
1619   EJDB_EXEC ux = { 0 };
1620   bool has_count = jql_has_aggregate_count(q);
1621 
1622   // Trying to stop on paused stream
1623   // not in context of query execution
1624   // hence we can avoid unnecessary database locking
1625   // before start reading stream.
1626   work->rc = jn_stream_pause_guard(qs);
1627   RCGO(work->rc, finish);
1628   JNGO(ns, env, napi_reference_ref(env, qs->stream_ref, &refs), finish);
1629 
1630   if (qs->explain_cb_ref) {
1631     ux.log = iwxstr_new();
1632     if (!ux.log) {
1633       work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1634       goto finish;
1635     }
1636   }
1637 
1638   ux.q = q;
1639   ux.db = qs->jbn->db;
1640   ux.opaque = qs;
1641   ux.limit = qs->limit;
1642   if (!has_count) {
1643     ux.visitor = jn_jql_stream_visitor;
1644   }
1645 
1646   work->rc = ejdb_exec(&ux);
1647   RCGO(work->rc, finish);
1648 
1649   // Stream close event
1650   cs = malloc(sizeof(*cs));
1651   if (!cs) {
1652     work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1653     goto finish;
1654   }
1655   cs->has_count = has_count;
1656   cs->count = ux.cnt;
1657   cs->stream_ref = qs->stream_ref;
1658   cs->log = ux.log;
1659   cs->document_id = -1;
1660   cs->document = 0;
1661   ux.log = 0;
1662 
1663   ns = napi_call_threadsafe_function(qs->jbn->resultset_tsf, cs, napi_tsfn_blocking);
1664   if (ns) {
1665     work->rc = JN_ERROR_NAPI;
1666     work->ns = ns;
1667     goto finish;
1668   }
1669 
1670   refs = 0;
1671 
1672 finish:
1673   if (refs) {
1674     napi_reference_unref(env, qs->stream_ref, &refs);
1675   }
1676   if (ux.log) {
1677     iwxstr_destroy(ux.log);
1678   }
1679   if (work->rc) {
1680     jn_cs_destroy(&cs);
1681   }
1682 }
1683 
jn_jql_stream_complete(napi_env env,napi_status ns,void * data)1684 static void jn_jql_stream_complete(napi_env env, napi_status ns, void *data) {
1685   JNWORK work = data;
1686   JNQS qs = work->data;
1687   if (!ns) {
1688     ns = work->ns;
1689   }
1690   if (jn_resolve_pending_errors(env, ns, work)) {
1691     goto finish;
1692   }
1693   JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish);
1694   work->deferred = 0;
1695 
1696 finish:
1697   jn_work_destroy(env, &work);
1698   jn_jnqs_destroy_mt(env, &qs);
1699 }
1700 
1701 // this.jql._impl.jql_stream_destroy(this);
jn_jql_stream_destroy(napi_env env,napi_callback_info info)1702 static napi_value jn_jql_stream_destroy(napi_env env, napi_callback_info info) {
1703   void *data;
1704   size_t argc = 1;
1705   napi_value ret = jn_undefined(env), argv, this;
1706 
1707   napi_status ns = napi_get_cb_info(env, info, &argc, &argv, &this, &data);
1708   if (ns || (argc < 1)) {
1709     goto finish;
1710   }
1711 
1712   ns = napi_remove_wrap(env, argv, &data);
1713   if (ns || !data) {
1714     goto finish;
1715   }
1716 
1717   JNQS qs = data;
1718   jn_jnqs_destroy_mt(env, &qs);
1719 
1720 finish:
1721   return ret;
1722 }
1723 
jn_jql_stream_set_paused(napi_env env,napi_callback_info info,bool paused)1724 static napi_value jn_jql_stream_set_paused(napi_env env, napi_callback_info info, bool paused) {
1725   napi_status ns;
1726   napi_value argv, this;
1727   size_t argc = 1;
1728   void *data;
1729 
1730   JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish);
1731   if (argc != 1) {
1732     iwrc rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1733     JNRC(env, rc);
1734     goto finish;
1735   }
1736   JNGO(ns, env, napi_unwrap(env, argv, &data), finish);
1737   JNQS qs = data;
1738   assert(qs);
1739 
1740   int rci = pthread_mutex_lock(&qs->mtx);
1741   if (rci) {
1742     iwrc rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci);
1743     JNRC(env, rc);
1744     goto finish;
1745   }
1746   if (qs->paused != paused) {
1747     qs->paused = paused;
1748     pthread_cond_broadcast(&qs->cond);
1749   }
1750   pthread_mutex_unlock(&qs->mtx);
1751 
1752 finish:
1753   return jn_undefined(env);
1754 }
1755 
jn_jql_stream_pause(napi_env env,napi_callback_info info)1756 static napi_value jn_jql_stream_pause(napi_env env, napi_callback_info info) {
1757   return jn_jql_stream_set_paused(env, info, true);
1758 }
1759 
jn_jql_stream_resume(napi_env env,napi_callback_info info)1760 static napi_value jn_jql_stream_resume(napi_env env, napi_callback_info info) {
1761   return jn_jql_stream_set_paused(env, info, false);
1762 }
1763 
jn_jql_stream_abort(napi_env env,napi_callback_info info)1764 static napi_value jn_jql_stream_abort(napi_env env, napi_callback_info info) {
1765   napi_status ns;
1766   napi_value argv, this;
1767   size_t argc = 1;
1768   void *data;
1769   JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish);
1770   if (argc != 1) {
1771     iwrc rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1772     JNRC(env, rc);
1773     goto finish;
1774   }
1775   JNGO(ns, env, napi_unwrap(env, argv, &data), finish);
1776   JNQS qs = data;
1777   qs->aborted = true;
1778 finish:
1779   return jn_undefined(env);
1780 }
1781 
1782 // JQL._impl.jql_stream_attach(this, stream, [opts.limit, opts.explainCallback]);
jn_jql_stream_attach(napi_env env,napi_callback_info info)1783 static napi_value jn_jql_stream_attach(napi_env env, napi_callback_info info) {
1784   iwrc rc = 0;
1785   napi_status ns;
1786   napi_value ret = 0, argv[3], this, vexplain;
1787   size_t argc = sizeof(argv) / sizeof(argv[0]);
1788   void *data;
1789 
1790   JBN jbn;
1791   JNQL jnql;
1792   JNQS qs = 0;
1793 
1794   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
1795   if (argc != sizeof(argv) / sizeof(argv[0])) {
1796     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1797     goto finish;
1798   }
1799   JNGO(ns, env, napi_unwrap(env, argv[0], (void**) &jnql), finish);  // -V580
1800 
1801   qs = calloc(1, sizeof(*qs));
1802   if (!qs) {
1803     rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1804     goto finish;
1805   }
1806 
1807   pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
1808   pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
1809   memcpy(&qs->mtx, &mtx, sizeof(mtx));
1810   memcpy(&qs->cond, &cond, sizeof(cond));
1811 
1812   qs->paused = true;
1813   qs->jnql = jnql;
1814   qs->jnql->refs++; // Query in use
1815   qs->limit = jn_int_at(env, argv[2], true, false, 0, &rc);
1816   RCGO(rc, finish);
1817 
1818   JNGO(ns, env, napi_get_element(env, argv[2], 1, &vexplain), finish);
1819   if (!jn_is_null_or_undefined(env, vexplain)) {
1820     napi_valuetype vtype;
1821     JNGO(ns, env, napi_typeof(env, vexplain, &vtype), finish);
1822     if (vtype != napi_function) {
1823       rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1824       goto finish;
1825     }
1826     JNGO(ns, env, napi_create_reference(env, vexplain, 1, &qs->explain_cb_ref), finish);
1827   }
1828   JNGO(ns, env, napi_unwrap(env, this, (void**) &jbn), finish);                   // -V580
1829   JNGO(ns, env, napi_create_reference(env, argv[1], 1, &qs->stream_ref), finish); // Reference to stream
1830   JNGO(ns, env, napi_wrap(env, argv[1], qs, 0, 0, 0), finish);
1831 
1832   // Launch async work
1833   qs->refs = 2;     // +1 for jn_jql_stream_complete
1834   // +1 for jn_jql_stream_destroy
1835   qs->jbn = jbn;
1836   qs->work.data = qs;
1837   ret = jn_launch_promise(env, info, "query", jn_jql_stream_execute, jn_jql_stream_complete, &qs->work);
1838 
1839 finish:
1840   if (rc) {
1841     JNRC(env, rc);
1842     if (qs) {
1843       qs->refs = 0; // needed to destroy it completely
1844     }
1845     jn_jnqs_destroy_mt(env, &qs);
1846   }
1847   return ret ? ret : jn_undefined(env);
1848 }
1849 
jn_jql_free_set_string_value(void * ptr,void * op)1850 static void jn_jql_free_set_string_value(void *ptr, void *op) {
1851   IWPOOL *pool = op;
1852   if (pool) {
1853     iwpool_destroy(pool);
1854   }
1855 }
1856 
1857 // this._impl.jql_set(jql, placeholder, val, 1);
jn_jql_set(napi_env env,napi_callback_info info)1858 static napi_value jn_jql_set(napi_env env, napi_callback_info info) {
1859   iwrc rc = 0;
1860   int iplh = 0;
1861   napi_value argv[4], this;
1862   size_t argc = sizeof(argv) / sizeof(argv[0]);
1863   const char *splh = 0, *svalue;
1864   JNQL jnql;
1865   void *data;
1866   napi_status ns;
1867   napi_valuetype vtype;
1868 
1869   IWPOOL *vpool = 0; // jql_set_xxx value
1870   IWPOOL *pool = iwpool_create(32);
1871   if (!pool) {
1872     rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1873     goto finish;
1874   }
1875 
1876   JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish);
1877   if (argc != sizeof(argv) / sizeof(argv[0])) {
1878     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1879     goto finish;
1880   }
1881   JNGO(ns, env, napi_unwrap(env, argv[0], &data), finish);
1882   jnql = data;
1883   if (jnql->refs > 0) {
1884     rc = JN_ERROR_QUERY_IN_USE;
1885     goto finish;
1886   }
1887 
1888   // Set type
1889   int stype = jn_int(env, argv[3], false, false, &rc);
1890   RCGO(rc, finish);
1891 
1892   // Placeholder
1893   JNGO(ns, env, napi_typeof(env, argv[1], &vtype), finish);
1894 
1895   if (vtype == napi_string) {
1896     iplh = 0;
1897     splh = jn_string(env, argv[1], pool, false, false, &rc);
1898   } else if (vtype == napi_number) {
1899     splh = 0;
1900     iplh = jn_int(env, argv[1], false, false, &rc);
1901   } else {
1902     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1903   }
1904   RCGO(rc, finish);
1905 
1906   switch (stype) {
1907     case 6:   // String
1908     case 1:   // JSON
1909     case 2: { // Regexp
1910       JBL_NODE node;
1911       vpool = iwpool_create(64);
1912       if (!vpool) {
1913         rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
1914         goto finish;
1915       }
1916       RCGO(rc, finish);
1917       switch (stype) {
1918         case 6: // String
1919         case 2: // Regexp
1920           svalue = jn_string(env, argv[2], vpool, false, false, &rc);
1921           RCGO(rc, finish);
1922           if (stype == 6) {
1923             rc = jql_set_str2(jnql->jql, splh, iplh, svalue, jn_jql_free_set_string_value, vpool); // -V614
1924           } else {
1925             rc = jql_set_regexp2(jnql->jql, splh, iplh, svalue, jn_jql_free_set_string_value, vpool);
1926           }
1927           RCGO(rc, finish);
1928           break;
1929         case 1:
1930           svalue = jn_string(env, argv[2], pool, false, false, &rc);
1931           RCGO(rc, finish);
1932           rc = jbn_from_json(svalue, &node, vpool);
1933           RCGO(rc, finish);
1934           rc = jql_set_json2(jnql->jql, splh, iplh, node, jn_jql_free_set_string_value, vpool);
1935           RCGO(rc, finish);
1936           break;
1937       }
1938     }
1939     break;
1940 
1941     case 3: { // Integer
1942       int64_t v = jn_int(env, argv[2], false, false, &rc);
1943       RCGO(rc, finish);
1944       rc = jql_set_i64(jnql->jql, splh, iplh, v);
1945       RCGO(rc, finish);
1946     }
1947     break;
1948 
1949     case 4: { // Double
1950       double v = jn_double(env, argv[2], false, false, &rc);
1951       RCGO(rc, finish);
1952       rc = jql_set_f64(jnql->jql, splh, iplh, v);
1953       RCGO(rc, finish);
1954     }
1955     break;
1956 
1957     case 5: { // Boolean
1958       bool v = jn_bool(env, argv[2], false, false, &rc);
1959       RCGO(rc, finish);
1960       rc = jql_set_bool(jnql->jql, splh, iplh, v);
1961       RCGO(rc, finish);
1962     }
1963     break;
1964   }
1965 
1966 finish:
1967   if (pool) {
1968     iwpool_destroy(pool);
1969   }
1970   if (rc) {
1971     if (vpool) { // vpool will be destroyed when JQL freed
1972       iwpool_destroy(vpool);
1973     }
1974     JNRC(env, rc);
1975   }
1976   return jn_undefined(env);
1977 }
1978 
1979 // jql_limit(jql);
jn_jql_limit(napi_env env,napi_callback_info info)1980 static napi_value jn_jql_limit(napi_env env, napi_callback_info info) {
1981   iwrc rc = 0;
1982   napi_status ns;
1983   napi_value ret = 0, argv, this;
1984   size_t argc = 1;
1985   void *data;
1986   JNQL jnql;
1987   int64_t limit;
1988 
1989   JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish);
1990   if (argc != 1) {
1991     rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS;
1992     goto finish;
1993   }
1994   JNGO(ns, env, napi_unwrap(env, argv, &data), finish);
1995   jnql = data;
1996 
1997   rc = jql_get_limit(jnql->jql, &limit);
1998   RCGO(rc, finish);
1999 
2000   ret = jn_create_int64(env, limit);
2001 
2002 finish:
2003   if (rc) {
2004     JNRC(env, rc);
2005   }
2006   return ret ? ret : jn_undefined(env);
2007 }
2008 
2009 // ----------------
2010 
jn_ecodefn(locale_t locale,uint32_t ecode)2011 static const char *jn_ecodefn(locale_t locale, uint32_t ecode) {
2012   if (!((ecode > _JN_ERROR_START) && (ecode < _JN_ERROR_END))) {
2013     return 0;
2014   }
2015   switch (ecode) {
2016     case JN_ERROR_INVALID_NATIVE_CALL_ARGS:
2017       return "Invalid native function call args (JN_ERROR_INVALID_NATIVE_CALL_ARGS)";
2018     case JN_ERROR_INVALID_STATE:
2019       return "Invalid native extension state (JN_ERROR_INVALID_STATE)";
2020     case JN_ERROR_QUERY_IN_USE:
2021       return "Query object is in-use by active async iteration, and cannot be changed (JN_ERROR_QUERY_IN_USE)";
2022     case JN_ERROR_NAPI:
2023       return "N-API Error (JN_ERROR_NAPI)";
2024   }
2025   return 0;
2026 }
2027 
Init(napi_env env,napi_value exports)2028 napi_value Init(napi_env env, napi_value exports) {
2029   napi_status ns = 0;
2030   static volatile int jn_ecodefn_initialized = 0;
2031   if (__sync_bool_compare_and_swap(&jn_ecodefn_initialized, 0, 1)) {
2032     iwrc rc = ejdb_init();
2033     if (rc) {
2034       JNRC(env, rc);
2035       return 0;
2036     }
2037     iwlog_register_ecodefn(jn_ecodefn);
2038   }
2039   napi_value vglobal, vadd_streamfn;
2040 
2041   JNGO(ns, env, napi_get_global(env, &vglobal), finish);
2042 
2043   // Define EJDB2Impl class
2044   napi_value ejdb2impl_clazz;
2045   napi_property_descriptor properties[] = {
2046     JNFUNC(open),
2047     JNFUNC(close),
2048     JNFUNC(put),
2049     JNFUNC(patch),
2050     JNFUNC(patch_or_put),
2051     JNFUNC(get),
2052     JNFUNC(del),
2053     JNFUNC(rename_collection),
2054     JNFUNC(info),
2055     JNFUNC(index),
2056     JNFUNC(rmcoll),
2057     JNFUNC(online_backup),
2058     JNFUNC(jql_init),
2059     JNFUNC(jql_set),
2060     JNFUNC(jql_limit),
2061     JNFUNC(jql_stream_destroy),
2062     JNFUNC(jql_stream_attach),
2063     JNFUNC(jql_stream_pause),
2064     JNFUNC(jql_stream_resume),
2065     JNFUNC(jql_stream_abort)
2066   };
2067   JNGO(ns, env, napi_define_class(env, "EJDB2Impl", NAPI_AUTO_LENGTH, jn_ejdb2impl_ctor, 0,
2068                                   sizeof(properties) / sizeof(properties[0]),
2069                                   properties, &ejdb2impl_clazz), finish);
2070 
2071   napi_property_descriptor descriptors[] = {
2072     { "EJDB2Impl", 0, 0, 0, 0, ejdb2impl_clazz, napi_default, 0 }
2073   };
2074   JNGO(ns, env, napi_define_properties(env, exports,
2075                                        sizeof(descriptors) / sizeof(descriptors[0]),
2076                                        descriptors),
2077        finish);
2078 
2079   JNGO(ns, env, napi_get_named_property(env, vglobal, "__ejdb_add_stream_result__", &vadd_streamfn), finish);
2080   if (jn_is_null_or_undefined(env, vadd_streamfn)) {
2081     iwrc rc = JN_ERROR_INVALID_STATE;
2082     JNRC(env, rc);
2083     goto finish;
2084   }
2085   JNGO(ns, env, napi_create_reference(env, vadd_streamfn, 1, &k_vadd_streamfn_ref), finish);
2086 
2087 finish:
2088   return exports;
2089 }
2090 
2091 NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
2092