MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

implement LMDB database module, and transaction handling

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