MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include "ant.h"
4#include "errors.h"
5#include "internal.h"
6#include "modules/lmdb.h"
7#include "modules/buffer.h"
8#include "modules/symbol.h"
9#include "descriptors.h"
10#include "gc/modules.h"
11
12#include <lmdb.h>
13#include <stdbool.h>
14#include <stdint.h>
15#include <stdlib.h>
16#include <string.h>
17
18struct lmdb_env_handle {
19 MDB_env *env;
20 bool closed;
21 bool read_only;
22 char *path;
23 lmdb_db_handle_t *db_head;
24 lmdb_txn_handle_t *txn_head;
25 lmdb_env_handle_t *next_global;
26};
27
28struct lmdb_db_handle {
29 MDB_dbi dbi;
30 bool closed;
31 char *name;
32 lmdb_env_handle_t *env;
33 lmdb_db_handle_t *next_in_env;
34 lmdb_db_handle_t *next_global;
35};
36
37struct lmdb_txn_handle {
38 MDB_txn *txn;
39 bool closed;
40 bool read_only;
41 lmdb_env_handle_t *env;
42 lmdb_txn_handle_t *next_in_env;
43 lmdb_txn_handle_t *next_global;
44};
45
46struct lmdb_env_ref {
47 ant_value_t obj;
48 lmdb_env_handle_t *env;
49 lmdb_env_ref_t *next;
50};
51
52struct lmdb_db_ref {
53 ant_value_t obj;
54 lmdb_db_handle_t *db;
55 lmdb_db_ref_t *next;
56};
57
58struct lmdb_txn_ref {
59 ant_value_t obj;
60 lmdb_txn_handle_t *txn;
61 lmdb_txn_ref_t *next;
62};
63
64static lmdb_js_types_t lmdb_types = {0};
65static lmdb_env_handle_t *env_handles = NULL;
66static lmdb_db_handle_t *db_handles = NULL;
67static lmdb_txn_handle_t *txn_handles = NULL;
68static lmdb_env_ref_t *env_refs = NULL;
69static lmdb_db_ref_t *db_refs = NULL;
70static lmdb_txn_ref_t *txn_refs = NULL;
71
72static ant_value_t make_env_obj(ant_t *js, lmdb_env_handle_t *env);
73static ant_value_t make_db_obj(ant_t *js, lmdb_db_handle_t *db);
74static ant_value_t make_txn_obj(ant_t *js, lmdb_txn_handle_t *txn);
75
76static void list_remove_db(lmdb_env_handle_t *env, lmdb_db_handle_t *target) {
77 if (!env || !target) return;
78 lmdb_db_handle_t **cur = &env->db_head;
79 while (*cur) {
80 if (*cur == target) {
81 *cur = target->next_in_env;
82 target->next_in_env = NULL;
83 return;
84 }
85 cur = &(*cur)->next_in_env;
86 }
87}
88
89static void list_remove_txn(lmdb_env_handle_t *env, lmdb_txn_handle_t *target) {
90 if (!env || !target) return;
91 lmdb_txn_handle_t **cur = &env->txn_head;
92 while (*cur) {
93 if (*cur == target) {
94 *cur = target->next_in_env;
95 target->next_in_env = NULL;
96 return;
97 }
98 cur = &(*cur)->next_in_env;
99 }
100}
101
102static void register_env_ref(ant_value_t obj, lmdb_env_handle_t *env) {
103 lmdb_env_ref_t *ref = ant_calloc(sizeof(lmdb_env_ref_t));
104 if (!ref) return;
105 ref->obj = obj;
106 ref->env = env;
107 ref->next = env_refs;
108 env_refs = ref;
109}
110
111static void register_db_ref(ant_value_t obj, lmdb_db_handle_t *db) {
112 lmdb_db_ref_t *ref = ant_calloc(sizeof(lmdb_db_ref_t));
113 if (!ref) return;
114 ref->obj = obj;
115 ref->db = db;
116 ref->next = db_refs;
117 db_refs = ref;
118}
119
120static void register_txn_ref(ant_value_t obj, lmdb_txn_handle_t *txn) {
121 lmdb_txn_ref_t *ref = ant_calloc(sizeof(lmdb_txn_ref_t));
122 if (!ref) return;
123 ref->obj = obj;
124 ref->txn = txn;
125 ref->next = txn_refs;
126 txn_refs = ref;
127}
128
129static lmdb_env_handle_t *find_env_by_obj(ant_value_t obj) {
130 for (lmdb_env_ref_t *ref = env_refs; ref; ref = ref->next)
131 if (ref->obj == obj) return ref->env;
132 return NULL;
133}
134
135static lmdb_db_handle_t *find_db_by_obj(ant_value_t obj) {
136 for (lmdb_db_ref_t *ref = db_refs; ref; ref = ref->next)
137 if (ref->obj == obj) return ref->db;
138 return NULL;
139}
140
141static lmdb_txn_handle_t *find_txn_by_obj(ant_value_t obj) {
142 for (lmdb_txn_ref_t *ref = txn_refs; ref; ref = ref->next)
143 if (ref->obj == obj) return ref->txn;
144 return NULL;
145}
146
147static void unregister_env_ref_by_obj(ant_value_t obj) {
148 lmdb_env_ref_t **cur = &env_refs;
149 while (*cur) {
150 if ((*cur)->obj == obj) {
151 lmdb_env_ref_t *next = (*cur)->next;
152 free(*cur);
153 *cur = next;
154 return;
155 }
156 cur = &(*cur)->next;
157 }
158}
159
160static void unregister_db_ref_by_obj(ant_value_t obj) {
161 lmdb_db_ref_t **cur = &db_refs;
162 while (*cur) {
163 if ((*cur)->obj == obj) {
164 lmdb_db_ref_t *next = (*cur)->next;
165 free(*cur);
166 *cur = next;
167 return;
168 }
169 cur = &(*cur)->next;
170 }
171}
172
173static void unregister_txn_ref_by_obj(ant_value_t obj) {
174 lmdb_txn_ref_t **cur = &txn_refs;
175 while (*cur) {
176 if ((*cur)->obj == obj) {
177 lmdb_txn_ref_t *next = (*cur)->next;
178 free(*cur);
179 *cur = next;
180 return;
181 }
182 cur = &(*cur)->next;
183 }
184}
185
186static void unregister_db_refs_by_env(lmdb_env_handle_t *env) {
187 lmdb_db_ref_t **cur = &db_refs;
188 while (*cur) {
189 if ((*cur)->db && (*cur)->db->env == env) {
190 lmdb_db_ref_t *next = (*cur)->next;
191 free(*cur);
192 *cur = next;
193 continue;
194 }
195 cur = &(*cur)->next;
196 }
197}
198
199static void unregister_txn_refs_by_env(lmdb_env_handle_t *env) {
200 lmdb_txn_ref_t **cur = &txn_refs;
201 while (*cur) {
202 if ((*cur)->txn && (*cur)->txn->env == env) {
203 lmdb_txn_ref_t *next = (*cur)->next;
204 free(*cur);
205 *cur = next;
206 continue;
207 }
208 cur = &(*cur)->next;
209 }
210}
211
212static void env_handle_close(lmdb_env_handle_t *env) {
213 if (!env || env->closed) return;
214
215 lmdb_txn_handle_t *txn = env->txn_head;
216 while (txn) {
217 if (!txn->closed && txn->txn) {
218 mdb_txn_abort(txn->txn);
219 txn->txn = NULL;
220 txn->closed = true;
221 }
222 txn = txn->next_in_env;
223 }
224
225 lmdb_db_handle_t *db = env->db_head;
226 while (db) {
227 if (!db->closed) {
228 mdb_dbi_close(env->env, db->dbi);
229 db->closed = true;
230 }
231 db = db->next_in_env;
232 }
233
234 mdb_env_close(env->env);
235 env->env = NULL;
236 env->closed = true;
237 env->db_head = NULL;
238 env->txn_head = NULL;
239
240 unregister_db_refs_by_env(env);
241 unregister_txn_refs_by_env(env);
242}
243
244static lmdb_env_handle_t *get_env_handle(ant_t *js, ant_value_t obj, bool open_required) {
245 lmdb_env_handle_t *env = find_env_by_obj(obj);
246 if (!env) return NULL;
247 if (open_required && env->closed) return NULL;
248 return env;
249}
250
251static lmdb_db_handle_t *get_db_handle(ant_t *js, ant_value_t obj, bool open_required) {
252 lmdb_db_handle_t *db = find_db_by_obj(obj);
253 if (!db) return NULL;
254 if (open_required && (db->closed || !db->env || db->env->closed)) return NULL;
255 return db;
256}
257
258static lmdb_txn_handle_t *get_txn_handle(ant_t *js, ant_value_t obj, bool open_required) {
259 lmdb_txn_handle_t *txn = find_txn_by_obj(obj);
260 if (!txn) return NULL;
261 if (open_required && (txn->closed || !txn->txn)) return NULL;
262 return txn;
263}
264
265static bool option_bool(ant_t *js, ant_value_t options, const char *key, bool fallback) {
266 if (vtype(options) != T_OBJ) return fallback;
267 ant_value_t val = js_get(js, options, key);
268 if (vtype(val) == T_UNDEF) return fallback;
269 return js_truthy(js, val);
270}
271
272static unsigned int option_uint(ant_t *js, ant_value_t options, const char *key, unsigned int fallback) {
273 if (vtype(options) != T_OBJ) return fallback;
274 ant_value_t val = js_get(js, options, key);
275 if (vtype(val) != T_NUM) return fallback;
276 double n = js_getnum(val);
277 if (n < 0.0) return fallback;
278 return (unsigned int)n;
279}
280
281static size_t option_size(ant_t *js, ant_value_t options, const char *key, size_t fallback) {
282 if (vtype(options) != T_OBJ) return fallback;
283 ant_value_t val = js_get(js, options, key);
284 if (vtype(val) != T_NUM) return fallback;
285 double n = js_getnum(val);
286 if (n < 0.0) return fallback;
287 return (size_t)n;
288}
289
290static bool js_to_mdb_val(ant_t *js, ant_value_t input, MDB_val *out) {
291 if (vtype(input) == T_STR) {
292 size_t len = 0;
293 const char *str = js_getstr(js, input, &len);
294 if (!str) return false;
295 out->mv_data = (void *)str;
296 out->mv_size = len;
297 return true;
298 }
299
300 if (vtype(input) == T_OBJ) {
301 TypedArrayData *ta = buffer_get_typedarray_data(input);
302 if (ta) {
303 if (!ta->buffer || !ta->buffer->data) return false;
304 out->mv_data = (void *)(ta->buffer->data + ta->byte_offset);
305 out->mv_size = ta->byte_length;
306 return true;
307 }
308
309 ArrayBufferData *ab = buffer_get_arraybuffer_data(input);
310 if (ab) {
311 if (!ab->data) return false;
312 out->mv_data = (void *)ab->data;
313 out->mv_size = ab->length;
314 return true;
315 }
316 }
317
318 return false;
319}
320
321static ant_value_t mdb_val_to_js(ant_t *js, MDB_val *val, bool as_string) {
322 if (as_string) {
323 return js_mkstr(js, val->mv_data, val->mv_size);
324 }
325
326 ArrayBufferData *ab = create_array_buffer_data(val->mv_size);
327 if (!ab) return js_mkerr(js, "Failed to allocate LMDB read buffer");
328
329 if (val->mv_size > 0 && val->mv_data) {
330 memcpy(ab->data, val->mv_data, val->mv_size);
331 }
332
333 ant_value_t out = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, ab->length, "Uint8Array");
334 if (vtype(out) == T_ERR) free_array_buffer_data(ab);
335 return out;
336}
337
338static bool parse_get_encoding(ant_t *js, ant_value_t encoding, bool *as_string) {
339 if (vtype(encoding) == T_UNDEF) return true;
340 if (vtype(encoding) != T_STR) return false;
341
342 size_t len = 0;
343 const char *mode = js_getstr(js, encoding, &len);
344 if (!mode) return false;
345
346 if (len == 5 && memcmp(mode, "bytes", 5) == 0) {
347 *as_string = false;
348 return true;
349 }
350 if (len == 4 && memcmp(mode, "utf8", 4) == 0) {
351 *as_string = true;
352 return true;
353 }
354
355 return false;
356}
357
358static ant_value_t lmdb_open(ant_t *js, ant_value_t *args, int nargs) {
359 if (nargs < 1 || vtype(args[0]) != T_STR) {
360 return js_mkerr(js, "lmdb.open(path, options?) requires a string path");
361 }
362
363 size_t path_len = 0;
364 const char *path = js_getstr(js, args[0], &path_len);
365 if (!path || path_len == 0) {
366 return js_mkerr(js, "lmdb.open path cannot be empty");
367 }
368
369 ant_value_t options = nargs > 1 ? args[1] : js_mkundef();
370
371 bool read_only = option_bool(js, options, "readOnly", false);
372 bool no_subdir = option_bool(js, options, "noSubdir", false);
373 bool no_sync = option_bool(js, options, "noSync", false);
374 bool no_meta_sync = option_bool(js, options, "noMetaSync", false);
375 bool no_read_ahead = option_bool(js, options, "noReadAhead", false);
376 bool no_lock = option_bool(js, options, "noLock", false);
377 bool write_map = option_bool(js, options, "writeMap", false);
378 bool map_async = option_bool(js, options, "mapAsync", false);
379
380 size_t map_size = option_size(js, options, "mapSize", 0);
381 unsigned int max_readers = option_uint(js, options, "maxReaders", 0);
382 unsigned int max_dbs = option_uint(js, options, "maxDbs", 0);
383 unsigned int mode = option_uint(js, options, "mode", 0644U);
384
385 unsigned int flags = 0;
386 if (read_only) flags |= MDB_RDONLY;
387 if (no_subdir) flags |= MDB_NOSUBDIR;
388 if (no_sync) flags |= MDB_NOSYNC;
389 if (no_meta_sync) flags |= MDB_NOMETASYNC;
390 if (no_read_ahead) flags |= MDB_NORDAHEAD;
391 if (no_lock) flags |= MDB_NOLOCK;
392 if (write_map) flags |= MDB_WRITEMAP;
393 if (map_async) flags |= MDB_MAPASYNC;
394
395 MDB_env *env = NULL;
396 int rc = mdb_env_create(&env);
397 if (rc != 0) return js_mkerr(js, "lmdb_env_create failed: %s", mdb_strerror(rc));
398
399 if (map_size > 0) {
400 rc = mdb_env_set_mapsize(env, map_size);
401 if (rc != 0) {
402 mdb_env_close(env);
403 return js_mkerr(js, "lmdb_env_set_mapsize failed: %s", mdb_strerror(rc));
404 }
405 }
406
407 if (max_readers > 0) {
408 rc = mdb_env_set_maxreaders(env, max_readers);
409 if (rc != 0) {
410 mdb_env_close(env);
411 return js_mkerr(js, "lmdb_env_set_maxreaders failed: %s", mdb_strerror(rc));
412 }
413 }
414
415 if (max_dbs > 0) {
416 rc = mdb_env_set_maxdbs(env, max_dbs);
417 if (rc != 0) {
418 mdb_env_close(env);
419 return js_mkerr(js, "lmdb_env_set_maxdbs failed: %s", mdb_strerror(rc));
420 }
421 }
422
423 rc = mdb_env_open(env, path, flags, (mdb_mode_t)mode);
424 if (rc != 0) {
425 mdb_env_close(env);
426 return js_mkerr(js, "lmdb_env_open('%s') failed: %s", path, mdb_strerror(rc));
427 }
428
429 lmdb_env_handle_t *handle = ant_calloc(sizeof(lmdb_env_handle_t));
430 if (!handle) {
431 mdb_env_close(env);
432 return js_mkerr(js, "Out of memory");
433 }
434
435 handle->env = env;
436 handle->closed = false;
437 handle->read_only = read_only;
438 handle->path = strndup(path, path_len);
439 handle->next_global = env_handles;
440 env_handles = handle;
441
442 return make_env_obj(js, handle);
443}
444
445static ant_value_t lmdb_env_open_db(ant_t *js, ant_value_t *args, int nargs) {
446 lmdb_env_handle_t *env = get_env_handle(js, js_getthis(js), true);
447 if (!env) return js_mkerr(js, "Invalid or closed LMDB env");
448
449 const char *name = NULL;
450 size_t name_len = 0;
451 ant_value_t options = js_mkundef();
452
453 if (nargs > 0 && vtype(args[0]) == T_STR) {
454 name = js_getstr(js, args[0], &name_len);
455 if (nargs > 1) options = args[1];
456 } else if (nargs > 0 && vtype(args[0]) == T_OBJ) {
457 options = args[0];
458 }
459
460 bool create = option_bool(js, options, "create", false);
461 bool dup_sort = option_bool(js, options, "dupSort", false);
462 bool dup_fixed = option_bool(js, options, "dupFixed", false);
463 bool integer_key = option_bool(js, options, "integerKey", false);
464 bool integer_dup = option_bool(js, options, "integerDup", false);
465
466 if (env->read_only && create) {
467 return js_mkerr(js, "Cannot open DB with create=true on a read-only env");
468 }
469
470 unsigned int dbi_flags = 0;
471 if (create) dbi_flags |= MDB_CREATE;
472 if (dup_sort) dbi_flags |= MDB_DUPSORT;
473 if (dup_fixed) dbi_flags |= MDB_DUPFIXED;
474 if (integer_key) dbi_flags |= MDB_INTEGERKEY;
475 if (integer_dup) dbi_flags |= MDB_INTEGERDUP;
476
477 unsigned int txn_flags = env->read_only ? MDB_RDONLY : 0U;
478 MDB_txn *txn = NULL;
479 int rc = mdb_txn_begin(env->env, NULL, txn_flags, &txn);
480 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
481
482 MDB_dbi dbi = 0;
483 rc = mdb_dbi_open(txn, name, dbi_flags, &dbi);
484 if (rc != 0) {
485 mdb_txn_abort(txn);
486 return js_mkerr(js, "lmdb_dbi_open failed: %s", mdb_strerror(rc));
487 }
488
489 if (txn_flags & MDB_RDONLY) {
490 mdb_txn_abort(txn);
491 } else {
492 rc = mdb_txn_commit(txn);
493 if (rc != 0) return js_mkerr(js, "lmdb_txn_commit failed: %s", mdb_strerror(rc));
494 }
495
496 lmdb_db_handle_t *db = ant_calloc(sizeof(lmdb_db_handle_t));
497 if (!db) {
498 mdb_dbi_close(env->env, dbi);
499 return js_mkerr(js, "Out of memory");
500 }
501
502 db->dbi = dbi;
503 db->closed = false;
504 db->env = env;
505 db->name = name ? strndup(name, name_len) : NULL;
506
507 db->next_in_env = env->db_head;
508 env->db_head = db;
509 db->next_global = db_handles;
510 db_handles = db;
511
512 return make_db_obj(js, db);
513}
514
515static ant_value_t lmdb_env_begin_txn(ant_t *js, ant_value_t *args, int nargs) {
516 lmdb_env_handle_t *env = get_env_handle(js, js_getthis(js), true);
517 if (!env) return js_mkerr(js, "Invalid or closed LMDB env");
518
519 ant_value_t options = nargs > 0 ? args[0] : js_mkundef();
520 bool read_only = option_bool(js, options, "readOnly", env->read_only);
521
522 if (env->read_only && !read_only) {
523 return js_mkerr(js, "Cannot create read-write transaction on read-only env");
524 }
525
526 MDB_txn *txn = NULL;
527 int rc = mdb_txn_begin(env->env, NULL, read_only ? MDB_RDONLY : 0U, &txn);
528 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
529
530 lmdb_txn_handle_t *handle = ant_calloc(sizeof(lmdb_txn_handle_t));
531 if (!handle) {
532 mdb_txn_abort(txn);
533 return js_mkerr(js, "Out of memory");
534 }
535
536 handle->txn = txn;
537 handle->closed = false;
538 handle->read_only = read_only;
539 handle->env = env;
540
541 handle->next_in_env = env->txn_head;
542 env->txn_head = handle;
543 handle->next_global = txn_handles;
544 txn_handles = handle;
545
546 return make_txn_obj(js, handle);
547}
548
549static ant_value_t lmdb_env_close_method(ant_t *js, ant_value_t *args, int nargs) {
550 (void)args;
551 (void)nargs;
552 ant_value_t self = js_getthis(js);
553 lmdb_env_handle_t *env = get_env_handle(js, self, false);
554 if (!env) return js_mkerr(js, "Invalid LMDB env");
555
556 env_handle_close(env);
557 unregister_env_ref_by_obj(self);
558 return js_mkundef();
559}
560
561static ant_value_t lmdb_env_sync_method(ant_t *js, ant_value_t *args, int nargs) {
562 lmdb_env_handle_t *env = get_env_handle(js, js_getthis(js), true);
563 if (!env) return js_mkerr(js, "Invalid or closed LMDB env");
564
565 bool force = true;
566 if (nargs > 0) force = js_truthy(js, args[0]);
567
568 int rc = mdb_env_sync(env->env, force ? 1 : 0);
569 if (rc != 0) return js_mkerr(js, "lmdb_env_sync failed: %s", mdb_strerror(rc));
570 return js_mkundef();
571}
572
573static ant_value_t lmdb_env_stat_method(ant_t *js, ant_value_t *args, int nargs) {
574 lmdb_env_handle_t *env = get_env_handle(js, js_getthis(js), true);
575 if (!env) return js_mkerr(js, "Invalid or closed LMDB env");
576
577 MDB_txn *txn = NULL;
578 int rc = mdb_txn_begin(env->env, NULL, MDB_RDONLY, &txn);
579 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
580
581 MDB_stat stat;
582 rc = mdb_stat(txn, 0, &stat);
583 mdb_txn_abort(txn);
584 if (rc != 0) return js_mkerr(js, "lmdb_stat failed: %s", mdb_strerror(rc));
585
586 ant_value_t out = js_mkobj(js);
587 js_set(js, out, "psize", js_mknum((double)stat.ms_psize));
588 js_set(js, out, "depth", js_mknum((double)stat.ms_depth));
589 js_set(js, out, "branchPages", js_mknum((double)stat.ms_branch_pages));
590 js_set(js, out, "leafPages", js_mknum((double)stat.ms_leaf_pages));
591 js_set(js, out, "overflowPages", js_mknum((double)stat.ms_overflow_pages));
592 js_set(js, out, "entries", js_mknum((double)stat.ms_entries));
593
594 return out;
595}
596
597static ant_value_t lmdb_env_info_method(ant_t *js, ant_value_t *args, int nargs) {
598 lmdb_env_handle_t *env = get_env_handle(js, js_getthis(js), true);
599 if (!env) return js_mkerr(js, "Invalid or closed LMDB env");
600
601 MDB_envinfo info;
602 int rc = mdb_env_info(env->env, &info);
603 if (rc != 0) return js_mkerr(js, "lmdb_env_info failed: %s", mdb_strerror(rc));
604
605 ant_value_t out = js_mkobj(js);
606 js_set(js, out, "mapSize", js_mknum((double)info.me_mapsize));
607 js_set(js, out, "lastPgNo", js_mknum((double)info.me_last_pgno));
608 js_set(js, out, "lastTxnId", js_mknum((double)info.me_last_txnid));
609 js_set(js, out, "maxReaders", js_mknum((double)info.me_maxreaders));
610 js_set(js, out, "numReaders", js_mknum((double)info.me_numreaders));
611
612 return out;
613}
614
615static ant_value_t lmdb_txn_get_impl(ant_t *js, ant_value_t *args, int nargs, bool as_string) {
616 if (nargs < 2) return js_mkerr(js, "txn.getBytes/getString(db, key) requires db and key");
617
618 lmdb_txn_handle_t *txn = get_txn_handle(js, js_getthis(js), true);
619 if (!txn) return js_mkerr(js, "Invalid or closed LMDB transaction");
620
621 lmdb_db_handle_t *db = get_db_handle(js, args[0], true);
622 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
623 if (db->env != txn->env) return js_mkerr(js, "Database and transaction belong to different envs");
624
625 MDB_val key;
626 if (!js_to_mdb_val(js, args[1], &key)) {
627 return js_mkerr(js, "LMDB key must be string, ArrayBuffer, or TypedArray");
628 }
629
630 MDB_val value;
631 int rc = mdb_get(txn->txn, db->dbi, &key, &value);
632 if (rc == MDB_NOTFOUND) return js_mkundef();
633 if (rc != 0) return js_mkerr(js, "lmdb_get failed: %s", mdb_strerror(rc));
634
635 return mdb_val_to_js(js, &value, as_string);
636}
637
638static ant_value_t lmdb_txn_get(ant_t *js, ant_value_t *args, int nargs) {
639 if (nargs < 2) return js_mkerr(js, "txn.get(db, key, encoding?) requires db and key");
640 bool as_string = false;
641 if (nargs > 2 && !parse_get_encoding(js, args[2], &as_string)) {
642 return js_mkerr(js, "txn.get encoding must be 'utf8' or 'bytes'");
643 }
644 return lmdb_txn_get_impl(js, args, nargs, as_string);
645}
646
647static ant_value_t lmdb_txn_get_bytes(ant_t *js, ant_value_t *args, int nargs) {
648 return lmdb_txn_get_impl(js, args, nargs, false);
649}
650
651static ant_value_t lmdb_txn_get_string(ant_t *js, ant_value_t *args, int nargs) {
652 return lmdb_txn_get_impl(js, args, nargs, true);
653}
654
655static ant_value_t lmdb_txn_put(ant_t *js, ant_value_t *args, int nargs) {
656 if (nargs < 3) return js_mkerr(js, "txn.put(db, key, value, options?) requires db, key, and value");
657
658 lmdb_txn_handle_t *txn = get_txn_handle(js, js_getthis(js), true);
659 if (!txn) return js_mkerr(js, "Invalid or closed LMDB transaction");
660 if (txn->read_only) return js_mkerr(js, "Cannot write in read-only transaction");
661
662 lmdb_db_handle_t *db = get_db_handle(js, args[0], true);
663 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
664 if (db->env != txn->env) return js_mkerr(js, "Database and transaction belong to different envs");
665
666 MDB_val key;
667 MDB_val value;
668 if (!js_to_mdb_val(js, args[1], &key)) return js_mkerr(js, "LMDB key must be string, ArrayBuffer, or TypedArray");
669 if (!js_to_mdb_val(js, args[2], &value)) return js_mkerr(js, "LMDB value must be string, ArrayBuffer, or TypedArray");
670
671 ant_value_t options = nargs > 3 ? args[3] : js_mkundef();
672 unsigned int flags = 0;
673 if (option_bool(js, options, "noOverwrite", false)) flags |= MDB_NOOVERWRITE;
674 if (option_bool(js, options, "noDupData", false)) flags |= MDB_NODUPDATA;
675 if (option_bool(js, options, "append", false)) flags |= MDB_APPEND;
676 if (option_bool(js, options, "appendDup", false)) flags |= MDB_APPENDDUP;
677
678 int rc = mdb_put(txn->txn, db->dbi, &key, &value, flags);
679 if (rc == MDB_KEYEXIST) return js_false;
680 if (rc != 0) return js_mkerr(js, "lmdb_put failed: %s", mdb_strerror(rc));
681 return js_true;
682}
683
684static ant_value_t lmdb_txn_del(ant_t *js, ant_value_t *args, int nargs) {
685 if (nargs < 2) return js_mkerr(js, "txn.del(db, key, value?) requires db and key");
686
687 lmdb_txn_handle_t *txn = get_txn_handle(js, js_getthis(js), true);
688 if (!txn) return js_mkerr(js, "Invalid or closed LMDB transaction");
689 if (txn->read_only) return js_mkerr(js, "Cannot delete in read-only transaction");
690
691 lmdb_db_handle_t *db = get_db_handle(js, args[0], true);
692 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
693 if (db->env != txn->env) return js_mkerr(js, "Database and transaction belong to different envs");
694
695 MDB_val key;
696 if (!js_to_mdb_val(js, args[1], &key)) return js_mkerr(js, "LMDB key must be string, ArrayBuffer, or TypedArray");
697
698 MDB_val value;
699 MDB_val *value_ptr = NULL;
700 if (nargs > 2 && vtype(args[2]) != T_UNDEF) {
701 if (!js_to_mdb_val(js, args[2], &value)) return js_mkerr(js, "LMDB value must be string, ArrayBuffer, or TypedArray");
702 value_ptr = &value;
703 }
704
705 int rc = mdb_del(txn->txn, db->dbi, &key, value_ptr);
706 if (rc == MDB_NOTFOUND) return js_false;
707 if (rc != 0) return js_mkerr(js, "lmdb_del failed: %s", mdb_strerror(rc));
708 return js_true;
709}
710
711static ant_value_t lmdb_txn_commit(ant_t *js, ant_value_t *args, int nargs) {
712 ant_value_t self = js_getthis(js);
713 lmdb_txn_handle_t *txn = get_txn_handle(js, self, true);
714 if (!txn) return js_mkerr(js, "Invalid or closed LMDB transaction");
715
716 int rc = txn->read_only ? MDB_SUCCESS : mdb_txn_commit(txn->txn);
717 if (txn->read_only) mdb_txn_abort(txn->txn);
718
719 txn->txn = NULL;
720 txn->closed = true;
721 if (txn->env) list_remove_txn(txn->env, txn);
722 unregister_txn_ref_by_obj(self);
723
724 if (rc != 0) return js_mkerr(js, "lmdb_txn_commit failed: %s", mdb_strerror(rc));
725 return js_mkundef();
726}
727
728static ant_value_t lmdb_txn_abort(ant_t *js, ant_value_t *args, int nargs) {
729 ant_value_t self = js_getthis(js);
730 lmdb_txn_handle_t *txn = get_txn_handle(js, self, false);
731 if (!txn) return js_mkerr(js, "Invalid LMDB transaction");
732 if (txn->closed || !txn->txn) {
733 unregister_txn_ref_by_obj(self);
734 return js_mkundef();
735 }
736
737 mdb_txn_abort(txn->txn);
738 txn->txn = NULL;
739 txn->closed = true;
740 if (txn->env) list_remove_txn(txn->env, txn);
741 unregister_txn_ref_by_obj(self);
742 return js_mkundef();
743}
744
745static ant_value_t lmdb_db_get_impl(ant_t *js, ant_value_t *args, int nargs, bool as_string) {
746 if (nargs < 1) return js_mkerr(js, "db.getBytes/getString(key) requires key");
747 lmdb_db_handle_t *db = get_db_handle(js, js_getthis(js), true);
748 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
749
750 MDB_txn *txn = NULL;
751 int rc = mdb_txn_begin(db->env->env, NULL, MDB_RDONLY, &txn);
752 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
753
754 MDB_val key;
755 MDB_val value;
756 if (!js_to_mdb_val(js, args[0], &key)) {
757 mdb_txn_abort(txn);
758 return js_mkerr(js, "LMDB key must be string, ArrayBuffer, or TypedArray");
759 }
760
761 rc = mdb_get(txn, db->dbi, &key, &value);
762 if (rc == MDB_NOTFOUND) {
763 mdb_txn_abort(txn);
764 return js_mkundef();
765 }
766 if (rc != 0) {
767 mdb_txn_abort(txn);
768 return js_mkerr(js, "lmdb_get failed: %s", mdb_strerror(rc));
769 }
770
771 ant_value_t out = mdb_val_to_js(js, &value, as_string);
772 mdb_txn_abort(txn);
773 return out;
774}
775
776static ant_value_t lmdb_db_get(ant_t *js, ant_value_t *args, int nargs) {
777 if (nargs < 1) return js_mkerr(js, "db.get(key, encoding?) requires key");
778 bool as_string = false;
779 if (nargs > 1 && !parse_get_encoding(js, args[1], &as_string)) {
780 return js_mkerr(js, "db.get encoding must be 'utf8' or 'bytes'");
781 }
782 return lmdb_db_get_impl(js, args, nargs, as_string);
783}
784
785static ant_value_t lmdb_db_get_bytes(ant_t *js, ant_value_t *args, int nargs) {
786 return lmdb_db_get_impl(js, args, nargs, false);
787}
788
789static ant_value_t lmdb_db_get_string(ant_t *js, ant_value_t *args, int nargs) {
790 return lmdb_db_get_impl(js, args, nargs, true);
791}
792
793static ant_value_t lmdb_db_put(ant_t *js, ant_value_t *args, int nargs) {
794 if (nargs < 2) return js_mkerr(js, "db.put(key, value, options?) requires key and value");
795 lmdb_db_handle_t *db = get_db_handle(js, js_getthis(js), true);
796 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
797 if (db->env->read_only) return js_mkerr(js, "Cannot write on read-only LMDB env");
798
799 MDB_txn *txn = NULL;
800 int rc = mdb_txn_begin(db->env->env, NULL, 0, &txn);
801 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
802
803 MDB_val key;
804 MDB_val value;
805 if (!js_to_mdb_val(js, args[0], &key) || !js_to_mdb_val(js, args[1], &value)) {
806 mdb_txn_abort(txn);
807 return js_mkerr(js, "LMDB key/value must be string, ArrayBuffer, or TypedArray");
808 }
809
810 ant_value_t options = nargs > 2 ? args[2] : js_mkundef();
811 unsigned int flags = 0;
812 if (option_bool(js, options, "noOverwrite", false)) flags |= MDB_NOOVERWRITE;
813 if (option_bool(js, options, "noDupData", false)) flags |= MDB_NODUPDATA;
814 if (option_bool(js, options, "append", false)) flags |= MDB_APPEND;
815 if (option_bool(js, options, "appendDup", false)) flags |= MDB_APPENDDUP;
816
817 rc = mdb_put(txn, db->dbi, &key, &value, flags);
818 if (rc == MDB_KEYEXIST) {
819 mdb_txn_abort(txn);
820 return js_false;
821 }
822 if (rc != 0) {
823 mdb_txn_abort(txn);
824 return js_mkerr(js, "lmdb_put failed: %s", mdb_strerror(rc));
825 }
826
827 rc = mdb_txn_commit(txn);
828 if (rc != 0) return js_mkerr(js, "lmdb_txn_commit failed: %s", mdb_strerror(rc));
829 return js_true;
830}
831
832static ant_value_t lmdb_db_del(ant_t *js, ant_value_t *args, int nargs) {
833 if (nargs < 1) return js_mkerr(js, "db.del(key, value?) requires key");
834 lmdb_db_handle_t *db = get_db_handle(js, js_getthis(js), true);
835 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
836 if (db->env->read_only) return js_mkerr(js, "Cannot delete on read-only LMDB env");
837
838 MDB_txn *txn = NULL;
839 int rc = mdb_txn_begin(db->env->env, NULL, 0, &txn);
840 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
841
842 MDB_val key;
843 if (!js_to_mdb_val(js, args[0], &key)) {
844 mdb_txn_abort(txn);
845 return js_mkerr(js, "LMDB key must be string, ArrayBuffer, or TypedArray");
846 }
847
848 MDB_val value;
849 MDB_val *value_ptr = NULL;
850 if (nargs > 1 && vtype(args[1]) != T_UNDEF) {
851 if (!js_to_mdb_val(js, args[1], &value)) {
852 mdb_txn_abort(txn);
853 return js_mkerr(js, "LMDB value must be string, ArrayBuffer, or TypedArray");
854 }
855 value_ptr = &value;
856 }
857
858 rc = mdb_del(txn, db->dbi, &key, value_ptr);
859 if (rc == MDB_NOTFOUND) {
860 mdb_txn_abort(txn);
861 return js_false;
862 }
863 if (rc != 0) {
864 mdb_txn_abort(txn);
865 return js_mkerr(js, "lmdb_del failed: %s", mdb_strerror(rc));
866 }
867
868 rc = mdb_txn_commit(txn);
869 if (rc != 0) return js_mkerr(js, "lmdb_txn_commit failed: %s", mdb_strerror(rc));
870 return js_true;
871}
872
873static ant_value_t lmdb_db_clear(ant_t *js, ant_value_t *args, int nargs) {
874 lmdb_db_handle_t *db = get_db_handle(js, js_getthis(js), true);
875 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
876 if (db->env->read_only) return js_mkerr(js, "Cannot clear on read-only LMDB env");
877
878 MDB_txn *txn = NULL;
879 int rc = mdb_txn_begin(db->env->env, NULL, 0, &txn);
880 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
881
882 rc = mdb_drop(txn, db->dbi, 0);
883 if (rc != 0) {
884 mdb_txn_abort(txn);
885 return js_mkerr(js, "lmdb_drop(clear) failed: %s", mdb_strerror(rc));
886 }
887
888 rc = mdb_txn_commit(txn);
889 if (rc != 0) return js_mkerr(js, "lmdb_txn_commit failed: %s", mdb_strerror(rc));
890 return js_mkundef();
891}
892
893static ant_value_t lmdb_db_drop(ant_t *js, ant_value_t *args, int nargs) {
894 ant_value_t self = js_getthis(js);
895 lmdb_db_handle_t *db = get_db_handle(js, self, true);
896 if (!db) return js_mkerr(js, "Invalid or closed LMDB database handle");
897 if (db->env->read_only) return js_mkerr(js, "Cannot drop on read-only LMDB env");
898
899 bool del_db = true;
900 if (nargs > 0 && vtype(args[0]) == T_OBJ) {
901 del_db = option_bool(js, args[0], "delete", true);
902 } else if (nargs > 0 && vtype(args[0]) != T_UNDEF) {
903 del_db = js_truthy(js, args[0]);
904 }
905
906 MDB_txn *txn = NULL;
907 int rc = mdb_txn_begin(db->env->env, NULL, 0, &txn);
908 if (rc != 0) return js_mkerr(js, "lmdb_txn_begin failed: %s", mdb_strerror(rc));
909
910 rc = mdb_drop(txn, db->dbi, del_db ? 1 : 0);
911 if (rc != 0) {
912 mdb_txn_abort(txn);
913 return js_mkerr(js, "lmdb_drop failed: %s", mdb_strerror(rc));
914 }
915
916 rc = mdb_txn_commit(txn);
917 if (rc != 0) return js_mkerr(js, "lmdb_txn_commit failed: %s", mdb_strerror(rc));
918
919 if (del_db) {
920 db->closed = true;
921 if (db->env) list_remove_db(db->env, db);
922 unregister_db_ref_by_obj(self);
923 }
924 return js_mkundef();
925}
926
927static ant_value_t lmdb_db_close(ant_t *js, ant_value_t *args, int nargs) {
928 (void)args;
929 (void)nargs;
930 ant_value_t self = js_getthis(js);
931 lmdb_db_handle_t *db = get_db_handle(js, self, false);
932 if (!db) return js_mkerr(js, "Invalid LMDB database handle");
933 if (db->closed) {
934 unregister_db_ref_by_obj(self);
935 return js_mkundef();
936 }
937
938 if (db->env && !db->env->closed) {
939 mdb_dbi_close(db->env->env, db->dbi);
940 list_remove_db(db->env, db);
941 }
942
943 db->closed = true;
944 unregister_db_ref_by_obj(self);
945 return js_mkundef();
946}
947
948static ant_value_t lmdb_strerror_fn(ant_t *js, ant_value_t *args, int nargs) {
949 if (nargs < 1 || vtype(args[0]) != T_NUM) {
950 return js_mkerr(js, "lmdb.strerror(code) requires a numeric code");
951 }
952 int code = (int)js_getnum(args[0]);
953 const char *err = mdb_strerror(code);
954 return js_mkstr(js, err, strlen(err));
955}
956
957static ant_value_t lmdb_env_constructor(ant_t *js, ant_value_t *args, int nargs) {
958 return js_mkerr(js, "LMDBEnv cannot be constructed directly; use lmdb.open()");
959}
960
961static ant_value_t lmdb_db_constructor(ant_t *js, ant_value_t *args, int nargs) {
962 return js_mkerr(js, "LMDBDatabase cannot be constructed directly; use env.openDB()");
963}
964
965static ant_value_t lmdb_txn_constructor(ant_t *js, ant_value_t *args, int nargs) {
966 return js_mkerr(js, "LMDBTxn cannot be constructed directly; use env.beginTxn()");
967}
968
969static void ensure_lmdb_prototypes(ant_t *js) {
970 if (lmdb_types.ready) return;
971 ant_value_t object_proto = js->sym.object_proto;
972
973 ant_value_t env_ctor_obj = js_mkobj(js);
974 ant_value_t env_proto = js_mkobj(js);
975
976 js_set_proto_init(env_proto, object_proto);
977 js_set(js, env_proto, "openDB", js_mkfun(lmdb_env_open_db));
978 js_set(js, env_proto, "beginTxn", js_mkfun(lmdb_env_begin_txn));
979 js_set(js, env_proto, "close", js_mkfun(lmdb_env_close_method));
980 js_set(js, env_proto, "sync", js_mkfun(lmdb_env_sync_method));
981 js_set(js, env_proto, "stat", js_mkfun(lmdb_env_stat_method));
982 js_set(js, env_proto, "info", js_mkfun(lmdb_env_info_method));
983 js_set_sym(js, env_proto, get_toStringTag_sym(), js_mkstr(js, "LMDBEnv", 7));
984 js_set_slot(env_ctor_obj, SLOT_CFUNC, js_mkfun(lmdb_env_constructor));
985 js_mkprop_fast(js, env_ctor_obj, "prototype", 9, env_proto);
986 js_mkprop_fast(js, env_ctor_obj, "name", 4, ANT_STRING("LMDBEnv"));
987 js_set_descriptor(js, env_ctor_obj, "name", 4, 0);
988
989 ant_value_t db_ctor_obj = js_mkobj(js);
990 ant_value_t db_proto = js_mkobj(js);
991
992 js_set_proto_init(db_proto, object_proto);
993 js_set(js, db_proto, "get", js_mkfun(lmdb_db_get));
994 js_set(js, db_proto, "getBytes", js_mkfun(lmdb_db_get_bytes));
995 js_set(js, db_proto, "getString", js_mkfun(lmdb_db_get_string));
996 js_set(js, db_proto, "put", js_mkfun(lmdb_db_put));
997 js_set(js, db_proto, "del", js_mkfun(lmdb_db_del));
998 js_set(js, db_proto, "clear", js_mkfun(lmdb_db_clear));
999 js_set(js, db_proto, "drop", js_mkfun(lmdb_db_drop));
1000 js_set(js, db_proto, "close", js_mkfun(lmdb_db_close));
1001 js_set_sym(js, db_proto, get_toStringTag_sym(), js_mkstr(js, "LMDBDatabase", 12));
1002 js_set_slot(db_ctor_obj, SLOT_CFUNC, js_mkfun(lmdb_db_constructor));
1003 js_mkprop_fast(js, db_ctor_obj, "prototype", 9, db_proto);
1004 js_mkprop_fast(js, db_ctor_obj, "name", 4, ANT_STRING("LMDBDatabase"));
1005 js_set_descriptor(js, db_ctor_obj, "name", 4, 0);
1006
1007 ant_value_t txn_ctor_obj = js_mkobj(js);
1008 ant_value_t txn_proto = js_mkobj(js);
1009
1010 js_set_proto_init(txn_proto, object_proto);
1011 js_set(js, txn_proto, "get", js_mkfun(lmdb_txn_get));
1012 js_set(js, txn_proto, "getBytes", js_mkfun(lmdb_txn_get_bytes));
1013 js_set(js, txn_proto, "getString", js_mkfun(lmdb_txn_get_string));
1014 js_set(js, txn_proto, "put", js_mkfun(lmdb_txn_put));
1015 js_set(js, txn_proto, "del", js_mkfun(lmdb_txn_del));
1016 js_set(js, txn_proto, "commit", js_mkfun(lmdb_txn_commit));
1017 js_set(js, txn_proto, "abort", js_mkfun(lmdb_txn_abort));
1018 js_set_sym(js, txn_proto, get_toStringTag_sym(), js_mkstr(js, "LMDBTxn", 7));
1019 js_set_slot(txn_ctor_obj, SLOT_CFUNC, js_mkfun(lmdb_txn_constructor));
1020 js_mkprop_fast(js, txn_ctor_obj, "prototype", 9, txn_proto);
1021 js_mkprop_fast(js, txn_ctor_obj, "name", 4, ANT_STRING("LMDBTxn"));
1022 js_set_descriptor(js, txn_ctor_obj, "name", 4, 0);
1023
1024 lmdb_types.env_ctor = js_obj_to_func(env_ctor_obj);
1025 lmdb_types.db_ctor = js_obj_to_func(db_ctor_obj);
1026 lmdb_types.txn_ctor = js_obj_to_func(txn_ctor_obj);
1027 lmdb_types.env_proto = env_proto;
1028 lmdb_types.db_proto = db_proto;
1029 lmdb_types.txn_proto = txn_proto;
1030 lmdb_types.ready = true;
1031}
1032
1033static ant_value_t make_env_obj(ant_t *js, lmdb_env_handle_t *env) {
1034 ensure_lmdb_prototypes(js);
1035 ant_value_t obj = js_mkobj(js);
1036 js_set_slot(obj, SLOT_DATA, ANT_PTR(env));
1037 register_env_ref(obj, env);
1038 if (is_special_object(lmdb_types.env_proto)) js_set_proto_init(obj, lmdb_types.env_proto);
1039 return obj;
1040}
1041
1042static ant_value_t make_db_obj(ant_t *js, lmdb_db_handle_t *db) {
1043 ensure_lmdb_prototypes(js);
1044 ant_value_t obj = js_mkobj(js);
1045 js_set_slot(obj, SLOT_DATA, ANT_PTR(db));
1046 register_db_ref(obj, db);
1047 if (is_special_object(lmdb_types.db_proto)) js_set_proto_init(obj, lmdb_types.db_proto);
1048 return obj;
1049}
1050
1051static ant_value_t make_txn_obj(ant_t *js, lmdb_txn_handle_t *txn) {
1052 ensure_lmdb_prototypes(js);
1053 ant_value_t obj = js_mkobj(js);
1054 js_set_slot(obj, SLOT_DATA, ANT_PTR(txn));
1055 register_txn_ref(obj, txn);
1056 if (is_special_object(lmdb_types.txn_proto)) js_set_proto_init(obj, lmdb_types.txn_proto);
1057 return obj;
1058}
1059
1060ant_value_t lmdb_library(ant_t *js) {
1061 ensure_lmdb_prototypes(js);
1062 ant_value_t lib = js_mkobj(js);
1063 js_set(js, lib, "open", js_mkfun(lmdb_open));
1064 js_set(js, lib, "strerror", js_mkfun(lmdb_strerror_fn));
1065 js_set(js, lib, "Env", lmdb_types.env_ctor);
1066 js_set(js, lib, "Database", lmdb_types.db_ctor);
1067 js_set(js, lib, "Txn", lmdb_types.txn_ctor);
1068
1069 int major = 0; int minor = 0; int patch = 0;
1070 const char *version = mdb_version(&major, &minor, &patch);
1071
1072 js_set(js, lib, "version", js_mkstr(js, version, strlen(version)));
1073 js_set(js, lib, "versionMajor", js_mknum((double)major));
1074 js_set(js, lib, "versionMinor", js_mknum((double)minor));
1075 js_set(js, lib, "versionPatch", js_mknum((double)patch));
1076
1077 ant_value_t constants = js_mkobj(js);
1078 js_set(js, constants, "NOOVERWRITE", js_mknum((double)MDB_NOOVERWRITE));
1079 js_set(js, constants, "NODUPDATA", js_mknum((double)MDB_NODUPDATA));
1080 js_set(js, constants, "APPEND", js_mknum((double)MDB_APPEND));
1081 js_set(js, constants, "APPENDDUP", js_mknum((double)MDB_APPENDDUP));
1082 js_set(js, constants, "NOSUBDIR", js_mknum((double)MDB_NOSUBDIR));
1083 js_set(js, constants, "NOSYNC", js_mknum((double)MDB_NOSYNC));
1084 js_set(js, constants, "NOMETASYNC", js_mknum((double)MDB_NOMETASYNC));
1085 js_set(js, constants, "WRITEMAP", js_mknum((double)MDB_WRITEMAP));
1086 js_set(js, constants, "MAPASYNC", js_mknum((double)MDB_MAPASYNC));
1087 js_set(js, constants, "NOTLS", js_mknum((double)MDB_NOTLS));
1088 js_set(js, constants, "NOLOCK", js_mknum((double)MDB_NOLOCK));
1089 js_set(js, constants, "NORDAHEAD", js_mknum((double)MDB_NORDAHEAD));
1090 js_set(js, constants, "NOMEMINIT", js_mknum((double)MDB_NOMEMINIT));
1091 js_set(js, lib, "constants", constants);
1092
1093 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "lmdb", 4));
1094 return lib;
1095}
1096
1097void gc_mark_lmdb(ant_t *js, gc_mark_fn mark) {
1098 if (lmdb_types.ready) {
1099 mark(js, lmdb_types.env_ctor);
1100 mark(js, lmdb_types.db_ctor);
1101 mark(js, lmdb_types.txn_ctor);
1102 mark(js, lmdb_types.env_proto);
1103 mark(js, lmdb_types.db_proto);
1104 mark(js, lmdb_types.txn_proto);
1105 }
1106 for (lmdb_env_ref_t *ref = env_refs; ref; ref = ref->next) mark(js, ref->obj);
1107 for (lmdb_db_ref_t *ref = db_refs; ref; ref = ref->next) mark(js, ref->obj);
1108 for (lmdb_txn_ref_t *ref = txn_refs; ref; ref = ref->next) mark(js, ref->obj);
1109}
1110
1111void cleanup_lmdb_module(void) {
1112 lmdb_txn_handle_t *txn = txn_handles;
1113 while (txn) {
1114 if (!txn->closed && txn->txn) {
1115 mdb_txn_abort(txn->txn);
1116 txn->txn = NULL;
1117 txn->closed = true;
1118 }
1119 txn = txn->next_global;
1120 }
1121
1122 lmdb_db_handle_t *db = db_handles;
1123 while (db) {
1124 if (!db->closed && db->env && !db->env->closed) {
1125 mdb_dbi_close(db->env->env, db->dbi);
1126 db->closed = true;
1127 }
1128 db = db->next_global;
1129 }
1130
1131 lmdb_env_handle_t *env = env_handles;
1132 while (env) {
1133 if (!env->closed) env_handle_close(env);
1134 env = env->next_global;
1135 }
1136
1137 txn = txn_handles;
1138 while (txn) {
1139 lmdb_txn_handle_t *next = txn->next_global;
1140 free(txn);
1141 txn = next;
1142 }
1143 txn_handles = NULL;
1144
1145 db = db_handles;
1146 while (db) {
1147 lmdb_db_handle_t *next = db->next_global;
1148 free(db->name);
1149 free(db);
1150 db = next;
1151 }
1152 db_handles = NULL;
1153
1154 env = env_handles;
1155 while (env) {
1156 lmdb_env_handle_t *next = env->next_global;
1157 free(env->path);
1158 free(env);
1159 env = next;
1160 }
1161 env_handles = NULL;
1162
1163 while (env_refs) {
1164 lmdb_env_ref_t *next = env_refs->next;
1165 free(env_refs);
1166 env_refs = next;
1167 }
1168
1169 while (db_refs) {
1170 lmdb_db_ref_t *next = db_refs->next;
1171 free(db_refs);
1172 db_refs = next;
1173 }
1174
1175 while (txn_refs) {
1176 lmdb_txn_ref_t *next = txn_refs->next;
1177 free(txn_refs);
1178 txn_refs = next;
1179 }
1180
1181 lmdb_types = (lmdb_js_types_t){0};
1182}