lmdb 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -4
  3. data/Rakefile +48 -0
  4. data/ext/lmdb_ext/extconf.rb +29 -10
  5. data/ext/lmdb_ext/lmdb_ext.c +1389 -908
  6. data/ext/lmdb_ext/lmdb_ext.h +36 -19
  7. data/lib/lmdb/database.rb +2 -1
  8. data/lib/lmdb/version.rb +1 -1
  9. data/lmdb.gemspec +4 -1
  10. data/spec/gc_torture_spec.rb +162 -0
  11. data/spec/helper.rb +3 -3
  12. data/spec/lmdb_spec.rb +152 -136
  13. data/spec/pseudo_transactions_spec.rb +237 -0
  14. data/vendor/liblmdb/VERSION +1 -0
  15. data/vendor/{libraries/liblmdb → liblmdb}/lmdb.h +18 -63
  16. data/vendor/{libraries/liblmdb → liblmdb}/mdb.c +583 -1480
  17. data/vendor/{libraries/liblmdb → liblmdb}/midl.c +0 -62
  18. data/vendor/{libraries/liblmdb → liblmdb}/midl.h +4 -16
  19. metadata +11 -34
  20. data/CONTRIBUTORS +0 -8
  21. data/behaviour.org +0 -35
  22. data/ext/lmdb_ext/prototypes.sh +0 -4
  23. data/vendor/libraries/liblmdb/.gitignore +0 -24
  24. data/vendor/libraries/liblmdb/COPYRIGHT +0 -20
  25. data/vendor/libraries/liblmdb/Doxyfile +0 -1631
  26. data/vendor/libraries/liblmdb/LICENSE +0 -47
  27. data/vendor/libraries/liblmdb/Makefile +0 -118
  28. data/vendor/libraries/liblmdb/intro.doc +0 -192
  29. data/vendor/libraries/liblmdb/mdb_copy.1 +0 -61
  30. data/vendor/libraries/liblmdb/mdb_copy.c +0 -84
  31. data/vendor/libraries/liblmdb/mdb_drop.1 +0 -40
  32. data/vendor/libraries/liblmdb/mdb_drop.c +0 -135
  33. data/vendor/libraries/liblmdb/mdb_dump.1 +0 -81
  34. data/vendor/libraries/liblmdb/mdb_dump.c +0 -319
  35. data/vendor/libraries/liblmdb/mdb_load.1 +0 -84
  36. data/vendor/libraries/liblmdb/mdb_load.c +0 -492
  37. data/vendor/libraries/liblmdb/mdb_stat.1 +0 -70
  38. data/vendor/libraries/liblmdb/mdb_stat.c +0 -264
  39. data/vendor/libraries/liblmdb/mtest.c +0 -177
  40. data/vendor/libraries/liblmdb/mtest2.c +0 -124
  41. data/vendor/libraries/liblmdb/mtest3.c +0 -133
  42. data/vendor/libraries/liblmdb/mtest4.c +0 -168
  43. data/vendor/libraries/liblmdb/mtest5.c +0 -135
  44. data/vendor/libraries/liblmdb/mtest6.c +0 -141
  45. data/vendor/libraries/liblmdb/sample-bdb.txt +0 -73
  46. data/vendor/libraries/liblmdb/sample-mdb.txt +0 -62
  47. data/vendor/libraries/liblmdb/tooltag +0 -27
@@ -5,7 +5,7 @@
5
5
 
6
6
  // ruby 2
7
7
  #include "ruby/thread.h"
8
- #define CALL_WITHOUT_GVL(func, data1, ubf, data2) \
8
+ #define CALL_WITHOUT_GVL(func, data1, ubf, data2) \
9
9
  rb_thread_call_without_gvl2(func, data1, ubf, data2)
10
10
 
11
11
  #else
@@ -13,47 +13,105 @@
13
13
  // ruby 193
14
14
  // Expose the API from internal.h:
15
15
  VALUE rb_thread_call_without_gvl(
16
- rb_blocking_function_t *func, void *data1,
17
- rb_unblock_function_t *ubf, void *data2);
18
- #define CALL_WITHOUT_GVL(func, data1, ubf, data2) \
16
+ rb_blocking_function_t *func, void *data1,
17
+ rb_unblock_function_t *ubf, void *data2);
18
+ #define CALL_WITHOUT_GVL(func, data1, ubf, data2) \
19
19
  rb_thread_call_without_gvl((rb_blocking_function_t *)func, data1, ubf, data2)
20
20
 
21
21
  #endif
22
22
 
23
23
  static void check(int code) {
24
- if (!code)
25
- return;
24
+ if (!code)
25
+ return;
26
26
 
27
- const char* err = mdb_strerror(code);
28
- const char* sep = strchr(err, ':');
29
- // increment the offset by two in case there is a colon (plus space)
30
- if (sep)
31
- err = sep + 2;
27
+ const char* err = mdb_strerror(code);
28
+ const char* sep = strchr(err, ':');
29
+ // increment the offset by two in case there is a colon (plus space)
30
+ if (sep)
31
+ err = sep + 2;
32
32
 
33
33
  #define ERROR(name) if (code == MDB_##name) rb_raise(cError_##name, "%s", err);
34
34
  #include "errors.h"
35
35
  #undef ERROR
36
36
 
37
- rb_raise(cError, "%s", err); /* fallback */
37
+ rb_raise(cError, "%s", err); /* fallback */
38
38
  }
39
39
 
40
- static void transaction_free(Transaction* transaction) {
40
+ #ifndef REXISTS
41
+ #define REXISTS(x) (x && !NIL_P(x))
42
+ #endif
43
+
44
+ static void transaction_free(void* ptr) {
45
+ Transaction *transaction = (Transaction *)ptr;
46
+ if (transaction) {
41
47
  if (transaction->txn) {
42
- //int id = (int)mdb_txn_id(transaction->txn);
43
- //rb_warn(sprintf("Memory leak: Garbage collecting active transaction %d", id));
44
- rb_warn("Memory leak: Garbage collecting active transaction");
45
- // transaction_abort(transaction);
46
- // mdb_txn_abort(transaction->txn);
48
+ //int id = (int)mdb_txn_id(transaction->txn);
49
+ //rb_warn(sprintf("Memory leak: Garbage collecting active transaction %d", id));
50
+ rb_warn("Memory leak: Garbage collecting active transaction");
51
+ // transaction_abort(transaction);
52
+ // mdb_txn_abort(transaction->txn);
47
53
  }
48
- free(transaction);
54
+ xfree(transaction);
55
+ }
56
+ }
57
+
58
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
59
+ static void transaction_mark(void* ptr) {
60
+ Transaction *transaction = (Transaction *)ptr;
61
+ if (transaction) {
62
+ //GC_MARK(transaction->env);
63
+ GC_MARK_MOVABLE(transaction->env);
64
+ GC_MARK_MOVABLE(transaction->parent);
65
+ GC_MARK_MOVABLE(transaction->child);
66
+ GC_MARK_MOVABLE(transaction->thread);
67
+ GC_MARK_MOVABLE(transaction->cursors);
68
+ }
69
+ }
70
+
71
+ static void transaction_compact(void* ptr) {
72
+ Transaction *transaction = (Transaction *)ptr;
73
+ if (transaction) {
74
+ GC_LOCATION(transaction->env);
75
+ GC_LOCATION(transaction->parent);
76
+ GC_LOCATION(transaction->child);
77
+ GC_LOCATION(transaction->thread);
78
+ GC_LOCATION(transaction->cursors);
79
+ }
49
80
  }
50
81
 
82
+ /* Define the modern TypedData specifications */
83
+ static const rb_data_type_t lmdb_transaction_type = {
84
+ .wrap_struct_name = "LMDB::Transaction",
85
+ .function = {
86
+ .dmark = transaction_mark,
87
+ .dfree = transaction_free, // points to your existing free/abort code
88
+ .dsize = NULL,
89
+ .dcompact = transaction_compact, // <-- The absolute antidote to T_NONE crashes
90
+ },
91
+ .flags = 0
92
+ };
93
+
94
+ static const rb_data_type_t lmdb_environment_type;
95
+ static const rb_data_type_t lmdb_database_type;
96
+ static const rb_data_type_t lmdb_cursor_type;
97
+
98
+ /*
99
+ static VALUE transaction_compact_m(VALUE self) {
100
+ TRANSACTION(self, transaction);
101
+ transaction_compact(transaction);
102
+ return Qnil;
103
+ }
104
+ */
105
+
106
+ #else
51
107
  static void transaction_mark(Transaction* transaction) {
52
- rb_gc_mark(transaction->parent);
53
- rb_gc_mark(transaction->child);
54
- rb_gc_mark(transaction->env);
55
- rb_gc_mark(transaction->cursors);
108
+ rb_gc_mark(transaction->env);
109
+ rb_gc_mark(transaction->parent);
110
+ rb_gc_mark(transaction->child);
111
+ rb_gc_mark(transaction->thread);
112
+ rb_gc_mark(transaction->cursors);
56
113
  }
114
+ #endif
57
115
 
58
116
  /**
59
117
  * Commit a transaction in process. Any subtransactions of this
@@ -81,8 +139,8 @@ static void transaction_mark(Transaction* transaction) {
81
139
  * end
82
140
  */
83
141
  static VALUE transaction_commit(VALUE self) {
84
- transaction_finish(self, 1);
85
- return Qnil;
142
+ transaction_finish(self, 1);
143
+ return Qnil;
86
144
  }
87
145
 
88
146
  /**
@@ -108,8 +166,8 @@ static VALUE transaction_commit(VALUE self) {
108
166
  * end
109
167
  */
110
168
  static VALUE transaction_abort(VALUE self) {
111
- transaction_finish(self, 0);
112
- return Qnil;
169
+ transaction_finish(self, 0);
170
+ return Qnil;
113
171
  }
114
172
 
115
173
  /**
@@ -122,8 +180,8 @@ static VALUE transaction_abort(VALUE self) {
122
180
  * end
123
181
  */
124
182
  static VALUE transaction_env(VALUE self) {
125
- TRANSACTION(self, transaction);
126
- return transaction->env;
183
+ TRANSACTION(self, &lmdb_transaction_type, transaction);
184
+ return transaction->env;
127
185
  }
128
186
 
129
187
  /**
@@ -132,9 +190,9 @@ static VALUE transaction_env(VALUE self) {
132
190
  * @return [false,true] whether the transaction is read-only.
133
191
  */
134
192
  static VALUE transaction_is_readonly(VALUE self) {
135
- TRANSACTION(self, transaction);
136
- //MDB_txn* txn = transaction->txn;
137
- return (transaction->flags & MDB_RDONLY) ? Qtrue : Qfalse;
193
+ TRANSACTION(self, &lmdb_transaction_type, transaction);
194
+ //MDB_txn* txn = transaction->txn;
195
+ return (transaction->flags & MDB_RDONLY) ? Qtrue : Qfalse;
138
196
  }
139
197
 
140
198
  /**
@@ -143,7 +201,7 @@ static VALUE transaction_is_readonly(VALUE self) {
143
201
  * @return [false,true] whether the transaction is finished.
144
202
  */
145
203
  static VALUE transaction_is_finished(VALUE self) {
146
- TRANSACTION(self, transaction);
204
+ TRANSACTION(self, &lmdb_transaction_type, transaction);
147
205
  // MDB_TXN_FINISHED
148
206
  return (transaction->flags & 0x01) ? Qtrue : Qfalse;
149
207
  }
@@ -154,120 +212,267 @@ static VALUE transaction_is_finished(VALUE self) {
154
212
  * @return [false,true] whether the transaction incurred an error.
155
213
  */
156
214
  static VALUE transaction_is_error(VALUE self) {
157
- TRANSACTION(self, transaction);
215
+ TRANSACTION(self, &lmdb_transaction_type, transaction);
158
216
  // MDB_TXN_ERROR
159
217
  return (transaction->flags & 0x02) ? Qtrue : Qfalse;
160
218
  }
161
219
 
220
+ #ifndef MDB_TXN_PSEUDO
221
+ #define MDB_TXN_PSEUDO 0x1000 /* not a real txn; reusing parent */
222
+ #endif
223
+
224
+ static void clear_cursors(Transaction* transaction) {
225
+ long i;
226
+ for (i=0; i<RARRAY_LEN(transaction->cursors); i++) {
227
+ VALUE cursor = RARRAY_AREF(transaction->cursors, i);
228
+ cursor_close(cursor);
229
+ }
230
+ rb_ary_clear(transaction->cursors);
231
+ }
162
232
 
163
233
  static void transaction_finish(VALUE self, int commit) {
164
- TRANSACTION(self, transaction);
234
+ TRANSACTION(self, &lmdb_transaction_type, transaction);
235
+
236
+ if (!transaction->txn)
237
+ rb_raise(cError, "Transaction is already terminated");
165
238
 
166
- if (!transaction->txn)
167
- rb_raise(cError, "Transaction is already terminated");
239
+ /* pseudo-transactions are transparent wrappers around a parent;
240
+ commit/abort are no-ops since the parent owns the real txn */
168
241
 
242
+ if (transaction->flags & MDB_TXN_PSEUDO) {
243
+ transaction->txn = NULL;
244
+ transaction->flags |= 0x01; // MDB_TXN_FINISHED
245
+
246
+ // clear cursors
247
+ clear_cursors(transaction);
248
+ }
249
+ else {
169
250
  if (transaction->thread != rb_thread_current())
170
- rb_raise(cError, "The thread closing the transaction "
171
- "is not the one that opened it");
251
+ rb_raise(cError, "The thread closing the transaction "
252
+ "is not the one that opened it");
172
253
 
173
254
  // ensure the transaction being closed is the active one
174
255
  VALUE p = environment_active_txn(transaction->env);
175
256
  while (!NIL_P(p) && p != self) {
176
- TRANSACTION(p, txn);
177
- p = txn->parent;
257
+ TRANSACTION(p, &lmdb_transaction_type, txn);
258
+ p = txn->parent;
178
259
  }
179
260
  // bail out if the transaction `self` is not the active one
180
261
  if (p != self)
181
- rb_raise(cError, "Transaction is not active");
262
+ rb_raise(cError, "Transaction is not active");
182
263
 
183
264
  // now eliminate the cursors
184
- long i;
185
- for (i=0; i<RARRAY_LEN(transaction->cursors); i++) {
186
- VALUE cursor = RARRAY_AREF(transaction->cursors, i);
187
- cursor_close(cursor);
188
- }
189
- rb_ary_clear(transaction->cursors);
265
+ clear_cursors(transaction);
190
266
 
191
267
  // now actually finish the internal transaction
192
- int ret = 0;
268
+ if (!commit || (transaction->flags & MDB_RDONLY))
269
+ mdb_txn_abort(transaction->txn);
270
+ else
271
+ check(mdb_txn_commit(transaction->txn));
272
+
273
+ /*
193
274
  if (commit)
194
- ret = mdb_txn_commit(transaction->txn);
275
+ check(mdb_txn_commit(transaction->txn));
195
276
  else
196
- mdb_txn_abort(transaction->txn);
277
+ mdb_txn_abort(transaction->txn);
278
+ */
197
279
 
198
280
  // eliminate child transactions
199
- if (transaction->child) {
200
- p = self; // again this is a VALUE
201
- Transaction* txn = transaction; // and this is the struct
202
-
203
- // descend into deepest child transaction
204
- do {
205
- p = txn->child;
206
- // this is TRANSACTION minus the declaration
207
- Data_Get_Struct(txn->child, Transaction, txn);
208
- } while (txn->child);
209
-
210
- // now we ascend back up
211
- while (p != self) {
212
- TRANSACTION(p, txn);
213
- txn->txn = 0;
214
- p = txn->parent;
215
- }
281
+ if (REXISTS(transaction->child)) {
282
+ p = self; // again this is a VALUE
283
+ Transaction* txn = transaction; // and this is the struct
284
+
285
+ // descend into deepest child transaction
286
+ do {
287
+ p = txn->child;
288
+ // this is TRANSACTION minus the declaration
289
+ TypedData_Get_Struct(txn->child, Transaction,
290
+ &lmdb_transaction_type, txn);
291
+ } while (REXISTS(txn->child));
292
+
293
+ // now we ascend back up
294
+ while (p != self) {
295
+ TRANSACTION(p, &lmdb_transaction_type, txn);
296
+ txn->txn = 0;
297
+ p = txn->parent;
298
+ }
216
299
  }
300
+
217
301
  transaction->txn = 0;
302
+ }
218
303
 
219
- // no more active read-write transaction; unset the registry
220
- if (!(transaction->flags & MDB_RDONLY) && !transaction->parent) {
221
- ENVIRONMENT(transaction->env, env);
222
- // maybe this should be Qnil, i dunno
223
- env->rw_txn_thread = (VALUE)NULL;
224
- }
304
+ // clear the parent's child pointer now that we're done
305
+ if (REXISTS(transaction->parent)) {
306
+ TRANSACTION(transaction->parent, &lmdb_transaction_type, tpar);
307
+ tpar->child = Qnil;
308
+ }
309
+
310
+ // now set the active transaction to the parent, if there is one
311
+ environment_set_active_txn(transaction->env, transaction->thread,
312
+ transaction->parent);
225
313
 
226
- // now set the active transaction to the parent, if there is one
227
- environment_set_active_txn(transaction->env, transaction->thread,
228
- transaction->parent);
314
+ // at the end of transaction_finish, after everything is done:
315
+ transaction->env = Qnil;
316
+ transaction->parent = Qnil;
317
+ transaction->child = Qnil;
318
+ transaction->thread = Qnil;
319
+ transaction->cursors = Qnil;
229
320
 
230
- check(ret);
231
321
  }
232
322
 
233
- // Ruby 1.8.7 compatibility
234
- #ifndef HAVE_RB_FUNCALL_PASSING_BLOCK
323
+ // original helpers
324
+
235
325
  static VALUE call_with_transaction_helper(VALUE arg) {
236
- #error "Not implemented"
326
+ HelperArgs* a = (HelperArgs*)arg;
327
+ return rb_funcall_passing_block(a->self, rb_intern(a->name), a->argc, a->argv);
237
328
  }
238
- #else
239
- static VALUE call_with_transaction_helper(VALUE arg) {
240
- HelperArgs* a = (HelperArgs*)arg;
241
- return rb_funcall_passing_block(a->self, rb_intern(a->name), a->argc, a->argv);
329
+
330
+ static VALUE call_with_transaction(VALUE venv, VALUE self, const char* name,
331
+ int argc, const VALUE* argv, int flags) {
332
+ /*
333
+ if (flags & MDB_RDONLY)
334
+ rb_warn("RO: calling `%s`", name);
335
+ else
336
+ rb_warn("RW: calling `%s`", name);
337
+ */
338
+
339
+ HelperArgs arg = { self, name, argc, argv };
340
+ return with_transaction(venv, call_with_transaction_helper, (VALUE)&arg, flags);
242
341
  }
243
- #endif
244
342
 
245
- static VALUE call_with_transaction(VALUE venv, VALUE self, const char* name, int argc, const VALUE* argv, int flags) {
246
- HelperArgs arg = { self, name, argc, argv };
247
- return with_transaction(venv, call_with_transaction_helper, (VALUE)&arg, flags);
343
+ /*
344
+
345
+ // these two are from gemini
346
+
347
+ static VALUE call_with_transaction_helper(VALUE anchor_array) {
348
+ VALUE self = rb_ary_entry(anchor_array, 0);
349
+ VALUE name = rb_ary_entry(anchor_array, 1);
350
+ VALUE args = rb_ary_entry(anchor_array, 2);
351
+
352
+ int argc = RARRAY_LENINT(args);
353
+ VALUE* argv = RARRAY_PTR(args);
354
+
355
+ return rb_funcall_passing_block(self, SYM2ID(name), argc, argv);
248
356
  }
249
357
 
358
+ static VALUE call_with_transaction(VALUE venv, VALUE self, const char* name,
359
+ int argc, const VALUE* argv, int flags) {
360
+ VALUE current_block = rb_block_given_p() ? rb_block_proc() : Qnil;
361
+ VALUE passed_arguments = rb_ary_new_from_values(argc, (VALUE *)argv);
362
+ VALUE anchor_array = rb_ary_new_from_args(4, self, ID2SYM(rb_intern(name)),
363
+ passed_arguments, current_block);
364
+
365
+ // 1. Thread-safety: Fetch or create a shield array scoped to the
366
+ // CURRENT executing thread
367
+ VALUE current_thread = rb_thread_current();
368
+ ID shield_key = rb_intern("__lmdb_gc_shield__");
369
+ VALUE thread_shield = rb_thread_local_aref(current_thread, shield_key);
370
+
371
+ if (NIL_P(thread_shield)) {
372
+ thread_shield = rb_ary_new();
373
+ rb_thread_local_aset(current_thread, shield_key, thread_shield);
374
+ }
375
+
376
+ // 2. Push our anchor into this thread's private shield
377
+ rb_ary_push(thread_shield, anchor_array);
378
+
379
+ // 3. Execute the transaction wrapper (safely drops GVL)
380
+ VALUE result = with_transaction(venv, call_with_transaction_helper,
381
+ anchor_array, flags);
382
+
383
+ // 4. Pop it back out safely
384
+ rb_ary_pop(thread_shield);
385
+
386
+ return result;
387
+ }
388
+ */
389
+
390
+ // and now your regularly scheduled program
391
+
250
392
  static void *call_txn_begin(void *arg) {
251
- TxnArgs *txn_args = arg;
252
- txn_args->result = mdb_txn_begin(txn_args->env,
253
- txn_args->parent, txn_args->flags, txn_args->htxn);
254
- if (txn_args->result == MDB_MAP_RESIZED) {
255
- check(mdb_env_set_mapsize(txn_args->env, 0));
256
- txn_args->result = mdb_txn_begin(txn_args->env,
257
- txn_args->parent, txn_args->flags, txn_args->htxn);
258
- }
259
- return (void *)NULL;
393
+ TxnArgs *txn_args = arg;
394
+ txn_args->result = mdb_txn_begin(txn_args->env, txn_args->parent,
395
+ txn_args->flags, txn_args->htxn);
396
+ if (txn_args->result == MDB_MAP_RESIZED) {
397
+ check(mdb_env_set_mapsize(txn_args->env, 0));
398
+ txn_args->result = mdb_txn_begin(txn_args->env, txn_args->parent,
399
+ txn_args->flags, txn_args->htxn);
400
+ }
401
+ else if ((txn_args->flags & MDB_RDONLY) && txn_args->result == EAGAIN) {
402
+
403
+ int tries = 100;
404
+
405
+ struct timeval delay;
406
+ delay.tv_sec = 0;
407
+ delay.tv_usec = 1000;
408
+
409
+ do {
410
+ int dead = 0;
411
+ check(mdb_reader_check(txn_args->env, &dead));
412
+
413
+ /*
414
+ if (dead > 0)
415
+ rb_warn("LMDB: Cleared %d dead readers.", dead);
416
+ */
417
+
418
+ if (dead == 0)
419
+ rb_thread_wait_for(delay);
420
+
421
+ txn_args->result = mdb_txn_begin(txn_args->env, txn_args->parent,
422
+ txn_args->flags, txn_args->htxn);
423
+ if (tries-- <= 0)
424
+ break;
425
+
426
+ } while (txn_args->result == EAGAIN);
427
+ }
428
+ else if (txn_args->result == EINVAL) {
429
+ unsigned int envflags = 0;
430
+ mdb_env_get_flags(txn_args->env, &envflags);
431
+ /* MDB_FATAL_ERROR is 0x80000000 in lmdb's internal me_flags —
432
+ note this is distinct from the flags returned by mdb_env_get_flags
433
+ which only returns the user-visible flags. we check it anyway
434
+ as a diagnostic hint. */
435
+ rb_warn("mdb_txn_begin EINVAL: env=%p parent=%p flags=%x "
436
+ "user_env_flags=%x code=%x",
437
+ (void*)txn_args->env,
438
+ (void*)txn_args->parent,
439
+ txn_args->flags,
440
+ envflags, txn_args->result);
441
+ }
442
+
443
+ return (void *)NULL;
260
444
  }
261
445
 
262
446
  static void stop_txn_begin(void *arg)
263
447
  {
264
- TxnArgs *txn_args = arg;
265
- // There's no way to stop waiting for mutex:
266
- // http://www.cognitus.net/faq/pthread/pthreadSemiFAQ_6.html
267
- // However, we can (and must) release the mutex as soon as we get it:
268
- txn_args->stop = 1;
448
+ TxnArgs *txn_args = arg;
449
+ /*
450
+ old commentary:
451
+
452
+ There's no way to stop waiting for mutex:
453
+ https://web.archive.org/web/20160413164623/http://www.cognitus.net/faq/pthread/pthreadSemiFAQ_6.html
454
+ However, we can (and must) release the mutex as soon as we get it:
455
+ */
456
+ /*
457
+ new (2026-06-24) commentary:
458
+
459
+ my interpretation of https://github.com/ruby/ruby/blob/master/thread.c#L1646
460
+ is that `rb_thread_call_without_gvl2` executes this only upon
461
+ receipt of an interrupt signal, and those will only happen
462
+ *before* executing `call_txn_begin`. so the best we can do here is
463
+ flag that the process has been aborted from the outside to
464
+ indicate that it may need cleaning up. this means we can't call
465
+ `mdb_txn_abort` here but rather in the caller (`with_transaction`).
466
+ */
467
+ txn_args->stop = 1;
269
468
  }
270
469
 
470
+ // adding the break so we can commit on break
471
+ #ifndef TAG_BREAK
472
+ #define TAG_BREAK 0x2
473
+ #endif
474
+
475
+
271
476
  /**
272
477
  * This is the code that opens transactions. Read-write transactions
273
478
  * have to be called outside the GVL because they will block otherwise.
@@ -306,153 +511,240 @@ static void stop_txn_begin(void *arg)
306
511
  *
307
512
  */
308
513
 
309
- static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flags) {
310
- ENVIRONMENT(venv, environment);
514
+ static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE),
515
+ VALUE arg, int flags) {
516
+ ENVIRONMENT(venv, &lmdb_environment_type, environment);
311
517
 
312
- MDB_txn* txn;
313
- TxnArgs txn_args;
518
+ VALUE thread = rb_thread_current();
314
519
 
315
- VALUE thread = rb_thread_current();
316
- VALUE vparent = environment_active_txn(venv);
520
+ // ATTEMPT TO OBTAIN PARENT TRANSACTION (which may be a pseudo)
317
521
 
318
- //
522
+ VALUE vparent = environment_active_txn(venv);
319
523
  Transaction* tparent = NULL;
320
- if (vparent && !NIL_P(vparent))
321
- Data_Get_Struct(vparent, Transaction, tparent);
524
+ if (REXISTS(vparent))
525
+ TypedData_Get_Struct(vparent, Transaction, &lmdb_transaction_type, tparent);
322
526
 
323
- /*
324
- * If the requested transaction is read-only and there is a parent
325
- * transaction (whether read-only or not), we need to use the
326
- * parent transaction.
327
- *
328
- * XXX except conceivably you could do a transaction (RO or RW)
329
- * that spawns a thread that opens another transaction.
330
- *
331
- * note we do *NOT* re-begin the parent transaction, nor do we
332
- * want to commit it
333
- *
334
- */
527
+ // (parent transaction will necessarily be on the same thread by
528
+ // dint of how it is looked up)
335
529
 
336
- if (tparent && flags & MDB_RDONLY) {
337
- // We are reusing the parent transaction.
530
+ // ACQUIRE THE TRANSACTION FROM LMDB
338
531
 
339
- int exception;
340
- VALUE ret = rb_protect(fn, NIL_P(arg) ? vparent : arg, &exception);
532
+ MDB_txn* txn = NULL;
533
+ TxnArgs txn_args;
341
534
 
342
- if (exception) {
343
- // this is a cargo cult; i just copied it from below
344
- if (vparent == environment_active_txn(venv)) transaction_abort(vparent);
345
- rb_jump_tag(exception);
346
- }
347
- return ret;
535
+ txn_args.env = environment->env;
536
+ txn_args.parent = tparent ? tparent->txn : NULL;
537
+ txn_args.flags = flags;
538
+ txn_args.htxn = &txn;
539
+ txn_args.result = 0;
540
+ txn_args.stop = 0;
541
+
542
+ if (flags & MDB_RDONLY) {
543
+ /*
544
+ if (tparent && !(tparent->flags & MDB_RDONLY))
545
+ rb_warn("open RO under RW");
546
+ */
547
+
548
+ // if (!tparent || !(tparent->flags & MDB_RDONLY)) call_txn_begin(&txn_args);
549
+ if (!tparent) call_txn_begin(&txn_args);
550
+ }
551
+ else if (tparent) {
552
+ /*
553
+ if (!(tparent->flags & MDB_RDONLY))
554
+ rb_warn("open RW under RW");
555
+ */
556
+
557
+ // can't put a writable transaction under a read-only one
558
+ if (tparent->flags & MDB_RDONLY)
559
+ rb_raise(cError, "Can't open an RW transaction under an RO");
560
+
561
+ // writable transaction thread must match current thread
562
+ if (!REXISTS(environment->rw_txn_thread))
563
+ rb_raise(cError, "INTERNAL: rw_txn_thread should be non-nil");
564
+
565
+ // what it says, lol
566
+ if (tparent->thread != thread || environment->rw_txn_thread != thread)
567
+ rb_raise(cError, "Can't open a nested transaction on a different thread");
568
+
569
+ // we don't need to skirt the gvl since we already have the mutex
570
+ call_txn_begin(&txn_args);
348
571
  }
349
572
  else {
350
- // We are creating a new transaction.
351
-
352
- // XXX note this is a cursed goto loop that could almost certainly
353
- // be rewritten as a do-while
354
- retry:
355
- txn = NULL;
356
-
357
- txn_args.env = environment->env;
358
- txn_args.parent = active_txn(venv); // this returns an MDB_txn
359
- txn_args.flags = flags;
360
- txn_args.htxn = &txn;
361
- txn_args.result = 0;
362
- txn_args.stop = 0;
363
-
364
- if (flags & MDB_RDONLY)
365
- call_txn_begin(&txn_args);
366
- else {
367
- if (tparent) {
368
- // first we have to determine if we're on the same thread
369
- // as the parent, which in turn must be the same as the
370
- // environment's registry for which thread has the
371
- // read-write transaction
372
- if (thread != tparent->thread ||
373
- thread != environment->rw_txn_thread)
374
- rb_raise(cError,
375
- "Attempt to nest transaction on a different thread");
573
+ // this should be NULL since this is a top-level transaction
574
+ if (REXISTS(environment->rw_txn_thread))
575
+ rb_raise(cError, "A write transaction is already open on thread %p",
576
+ (void *)environment->rw_txn_thread);
577
+
578
+ // if a top-level transaction is writable then we call without the
579
+ // GVL, which may not acquire right away
580
+ do {
581
+ // reset these since we don't know what state they're in
582
+ txn = NULL;
583
+ txn_args.result = 0;
584
+ txn_args.stop = 0;
585
+
586
+ // don't need the macro anymore, the call is exposed
587
+ rb_thread_call_without_gvl2(call_txn_begin, &txn_args,
588
+ stop_txn_begin, &txn_args);
589
+
590
+ if (!txn) {
591
+ // this is an actual exception
592
+ if (txn_args.result != 0 && txn_args.result != MDB_MAP_RESIZED)
593
+ check(txn_args.result);
376
594
  }
595
+ else if (txn_args.stop) {
596
+ // if we get here it's because `rb_thread_call_without_gvl2`
597
+ // caught an interrupt before running `call_txn_begin`
598
+ mdb_txn_abort(txn);
377
599
 
378
- // try to acquire the new transaction
379
- CALL_WITHOUT_GVL(call_txn_begin, &txn_args, stop_txn_begin, &txn_args);
380
-
381
- if (txn_args.stop || !txn) {
382
- // !txn is when rb_thread_call_without_gvl2
383
- // returns before calling txn_begin
384
- if (txn) {
385
- mdb_txn_abort(txn);
386
- txn_args.result = 0;
387
- }
388
-
389
- //rb_warn("got here lol");
390
- rb_thread_check_ints();
391
- goto retry; // in what cases do we get here?
600
+ txn = NULL;
601
+ txn_args.result = 0;
392
602
  }
393
603
 
394
- // set the thread
395
- environment->rw_txn_thread = thread;
396
- }
604
+ // do thread stuff
605
+ rb_thread_check_ints();
606
+ rb_thread_schedule();
607
+ } while (!txn);
608
+ }
609
+
610
+ if (txn_args.result != 0) {
611
+ // clean up because this is a raise
612
+ if (txn) mdb_txn_abort(txn);
397
613
 
398
- // this will raise unless result is zero
399
614
  check(txn_args.result);
615
+ }
400
616
 
401
- Transaction* transaction;
402
- VALUE vtxn = Data_Make_Struct(cTransaction, Transaction, transaction_mark,
403
- transaction_free, transaction);
404
- transaction->parent = vparent;
405
- transaction->env = venv;
406
- transaction->txn = txn;
407
- transaction->flags = flags;
408
- transaction->thread = rb_thread_current();
409
- transaction->cursors = rb_ary_new();
617
+ // we should have the transaction handle now, so set up the struct
410
618
 
411
- // set the parent's child to self
412
- if (tparent) tparent->child = vtxn;
619
+ Transaction* transaction;
620
+ VALUE vtxn = TypedData_Make_Struct(cTransaction, Transaction,
621
+ &lmdb_transaction_type, transaction);
413
622
 
414
- environment_set_active_txn(venv, transaction->thread, vtxn);
623
+ transaction->env = venv;
624
+ transaction->thread = thread;
625
+ transaction->parent = vparent;
626
+ transaction->child = Qnil;
627
+ transaction->cursors = rb_ary_new();
415
628
 
416
- // now we run the function in the transaction
417
- int exception;
418
- VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
629
+ // if ((flags & MDB_RDONLY) && tparent && (tparent->flags & MDB_RDONLY)) {
630
+ if (tparent && (flags & MDB_RDONLY)) {
631
+ transaction->txn = tparent->txn;
632
+ transaction->flags = tparent->flags | MDB_TXN_PSEUDO;
633
+ }
634
+ else {
635
+ transaction->txn = txn;
636
+ transaction->flags = flags; // | (flags & MDB_RDONLY ? MDB_TXN_PSEUDO : 0);
637
+ }
419
638
 
420
- if (exception) {
421
- // rb_warn("lol got exception");
422
- if (vtxn == environment_active_txn(venv))
423
- transaction_abort(vtxn);
424
- rb_jump_tag(exception);
425
- }
639
+ // set parent's child to self
640
+ if (tparent) tparent->child = vtxn;
641
+
642
+ // put the active transaction to me
643
+
644
+ environment_set_active_txn(venv, thread, vtxn);
645
+
646
+ // ACTUALLY EXECUTE THE TRANSACTION BODY
647
+
648
+ int exception = 0;
649
+ VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
650
+
651
+ // note the transaction could have been explicitly committed/aborted
652
+ // in the block
653
+
654
+ // CLEAN UP
655
+
656
+ // XXX very real possibility that the code in the block opens
657
+ // another transaction, although that transaction would be in
658
+ // another block, so it would be active
659
+
660
+ if (exception) {
426
661
  if (vtxn == environment_active_txn(venv))
427
- transaction_commit(vtxn);
428
- return ret;
662
+ transaction_finish(vtxn, exception == TAG_BREAK);
663
+
664
+ rb_jump_tag(exception);
429
665
  }
666
+
667
+ // commit if it hasn't already been explicitly done
668
+ if (vtxn == environment_active_txn(venv)) transaction_commit(vtxn);
669
+
670
+ return ret;
430
671
  }
431
672
 
432
673
  static void environment_check(Environment* environment) {
433
- if (!environment->env)
434
- rb_raise(cError, "Environment is closed");
435
- }
436
-
437
- static void environment_free(Environment *environment) {
438
- if (environment->env) {
439
- // rb_warn("Memory leak - Garbage collecting open environment");
440
- if (!RHASH_EMPTY_P(environment->txn_thread_hash)) {
441
- // If a transaction (or cursor) is open, its block is on the
442
- // stack, so it will not be collected, so environment_free
443
- // should not be called.
444
- rb_warn("Bug: closing environment with open transactions.");
445
- }
446
- mdb_env_close(environment->env);
447
- }
448
- free(environment);
674
+ if (!environment->env)
675
+ rb_raise(cError, "Environment is closed");
676
+ }
677
+
678
+ static void environment_free(void* ptr) {
679
+ Environment *environment = (Environment *)ptr;
680
+ if (environment->env) {
681
+ // rb_warn("Memory leak - Garbage collecting open environment");
682
+ if (!RHASH_EMPTY_P(environment->txn_thread_hash)) {
683
+ // If a transaction (or cursor) is open, its block is on the
684
+ // stack, so it will not be collected, so environment_free
685
+ // should not be called.
686
+ rb_warn("Bug: closing environment with open transactions.");
687
+
688
+ rb_hash_clear(environment->txn_thread_hash);
689
+ rb_hash_clear(environment->thread_txn_hash);
690
+ environment->rw_txn_thread = Qnil;
691
+ }
692
+ mdb_env_close(environment->env);
693
+ environment->env = NULL;
694
+ }
695
+ xfree(environment);
696
+ }
697
+
698
+
699
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
700
+ static void environment_mark(void *ptr) {
701
+ Environment *environment = (Environment *)ptr;
702
+ if (environment) {
703
+ /*
704
+ GC_MARK(environment->thread_txn_hash);
705
+ GC_MARK(environment->txn_thread_hash);
706
+ GC_MARK(environment->rw_txn_thread);
707
+ */
708
+ GC_MARK_MOVABLE(environment->thread_txn_hash);
709
+ GC_MARK_MOVABLE(environment->txn_thread_hash);
710
+ GC_MARK_MOVABLE(environment->rw_txn_thread);
711
+ }
449
712
  }
450
713
 
714
+ static void environment_compact(void *ptr) {
715
+ Environment *environment = (Environment *)ptr;
716
+ if (environment) {
717
+ GC_LOCATION(environment->thread_txn_hash);
718
+ GC_LOCATION(environment->txn_thread_hash);
719
+ GC_LOCATION(environment->rw_txn_thread);
720
+ }
721
+ }
451
722
 
723
+ /* Define the modern TypedData specifications */
724
+ static const rb_data_type_t lmdb_environment_type = {
725
+ .wrap_struct_name = "LMDB::Environment",
726
+ .function = {
727
+ .dmark = environment_mark,
728
+ .dfree = environment_free,
729
+ .dsize = NULL,
730
+ .dcompact = environment_compact,
731
+ },
732
+ .flags = 0
733
+ };
734
+
735
+ /*
736
+ static VALUE environment_compact_m(VALUE self) {
737
+ ENVIRONMENT(self, environment);
738
+ environment_compact(environment);
739
+ return Qnil;
740
+ }
741
+ */
742
+ #else
452
743
  static void environment_mark(Environment* environment) {
453
- rb_gc_mark(environment->thread_txn_hash);
454
- rb_gc_mark(environment->txn_thread_hash);
744
+ rb_gc_mark(environment->thread_txn_hash);
745
+ rb_gc_mark(environment->txn_thread_hash);
455
746
  }
747
+ #endif
456
748
 
457
749
  /**
458
750
  * @overload close
@@ -464,35 +756,61 @@ static void environment_mark(Environment* environment) {
464
756
  * env.close
465
757
  */
466
758
  static VALUE environment_close(VALUE self) {
467
- ENVIRONMENT(self, environment);
468
- mdb_env_close(environment->env);
469
- environment->env = 0;
470
- return Qnil;
759
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
760
+ mdb_env_close(environment->env);
761
+ environment->env = 0;
762
+ return Qnil;
763
+ }
764
+
765
+ static int reader_list_callback(const char* msg, void* ctx) {
766
+ rb_ary_push((VALUE)ctx, rb_str_new2(msg));
767
+ // rb_warn("reader: %s", msg);
768
+ return 0;
769
+ }
770
+
771
+ static VALUE environment_reader_list(VALUE self) {
772
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
773
+
774
+ VALUE ret = rb_ary_new();
775
+
776
+ mdb_reader_list(environment->env, reader_list_callback, (void*)ret);
777
+
778
+ //return Qnil;
779
+ return ret;
780
+ }
781
+
782
+ static VALUE environment_reader_check(VALUE self) {
783
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
784
+
785
+ int dead = 0;
786
+ mdb_reader_check(environment->env, &dead);
787
+
788
+ return INT2NUM(dead);
471
789
  }
472
790
 
473
791
  static VALUE stat2hash(const MDB_stat* stat) {
474
- VALUE ret = rb_hash_new();
792
+ VALUE ret = rb_hash_new();
475
793
 
476
794
  #define STAT_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), INT2NUM(stat->ms_##name))
477
- STAT_SET(psize);
478
- STAT_SET(depth);
479
- STAT_SET(branch_pages);
480
- STAT_SET(leaf_pages);
481
- STAT_SET(overflow_pages);
482
- STAT_SET(entries);
795
+ STAT_SET(psize);
796
+ STAT_SET(depth);
797
+ STAT_SET(branch_pages);
798
+ STAT_SET(leaf_pages);
799
+ STAT_SET(overflow_pages);
800
+ STAT_SET(entries);
483
801
  #undef STAT_SET
484
802
 
485
- return ret;
803
+ return ret;
486
804
  }
487
805
 
488
806
  static VALUE flags2hash(int flags) {
489
- VALUE ret = rb_hash_new();
807
+ VALUE ret = rb_hash_new();
490
808
 
491
809
  #define FLAG(const, name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), (flags & MDB_##const) == 0 ? Qfalse : Qtrue);
492
810
  #include "dbi_flags.h"
493
811
  #undef FLAG
494
812
 
495
- return ret;
813
+ return ret;
496
814
  }
497
815
 
498
816
  /**
@@ -507,10 +825,10 @@ static VALUE flags2hash(int flags) {
507
825
  * * +:entries+ Number of data items
508
826
  */
509
827
  static VALUE environment_stat(VALUE self) {
510
- ENVIRONMENT(self, environment);
511
- MDB_stat stat;
512
- check(mdb_env_stat(environment->env, &stat));
513
- return stat2hash(&stat);
828
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
829
+ MDB_stat stat;
830
+ check(mdb_env_stat(environment->env, &stat));
831
+ return stat2hash(&stat);
514
832
  }
515
833
 
516
834
  /**
@@ -525,23 +843,23 @@ static VALUE environment_stat(VALUE self) {
525
843
  * * +:numreaders+ Max readers slots in the environment
526
844
  */
527
845
  static VALUE environment_info(VALUE self) {
528
- MDB_envinfo info;
846
+ MDB_envinfo info;
529
847
 
530
- ENVIRONMENT(self, environment);
531
- check(mdb_env_info(environment->env, &info));
848
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
849
+ check(mdb_env_info(environment->env, &info));
532
850
 
533
- VALUE ret = rb_hash_new();
851
+ VALUE ret = rb_hash_new();
534
852
 
535
853
  #define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), SIZET2NUM((size_t)info.me_##name));
536
- INFO_SET(mapaddr);
537
- INFO_SET(mapsize);
538
- INFO_SET(last_pgno);
539
- INFO_SET(last_txnid);
540
- INFO_SET(maxreaders);
541
- INFO_SET(numreaders);
854
+ INFO_SET(mapaddr);
855
+ INFO_SET(mapsize);
856
+ INFO_SET(last_pgno);
857
+ INFO_SET(last_txnid);
858
+ INFO_SET(maxreaders);
859
+ INFO_SET(numreaders);
542
860
  #undef INFO_SET
543
861
 
544
- return ret;
862
+ return ret;
545
863
  }
546
864
 
547
865
  /**
@@ -557,10 +875,10 @@ static VALUE environment_info(VALUE self) {
557
875
  * @raise [Error] when there is an error creating the copy.
558
876
  */
559
877
  static VALUE environment_copy(VALUE self, VALUE path) {
560
- ENVIRONMENT(self, environment);
561
- VALUE expanded_path = rb_file_expand_path(path, Qnil);
562
- check(mdb_env_copy(environment->env, StringValueCStr(expanded_path)));
563
- return Qnil;
878
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
879
+ VALUE expanded_path = rb_file_expand_path(path, Qnil);
880
+ check(mdb_env_copy(environment->env, StringValueCStr(expanded_path)));
881
+ return Qnil;
564
882
  }
565
883
 
566
884
  /**
@@ -577,37 +895,37 @@ static VALUE environment_copy(VALUE self, VALUE path) {
577
895
  * asynchronous.
578
896
  */
579
897
  static VALUE environment_sync(int argc, VALUE *argv, VALUE self) {
580
- ENVIRONMENT(self, environment);
898
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
581
899
 
582
- VALUE force;
583
- rb_scan_args(argc, argv, "01", &force);
900
+ VALUE force;
901
+ rb_scan_args(argc, argv, "01", &force);
584
902
 
585
- check(mdb_env_sync(environment->env, RTEST(force)));
586
- return Qnil;
903
+ check(mdb_env_sync(environment->env, RTEST(force)));
904
+ return Qnil;
587
905
  }
588
906
 
589
907
  static int environment_options(VALUE key, VALUE value, EnvironmentOptions* options) {
590
- ID id = rb_to_id(key);
908
+ ID id = rb_to_id(key);
591
909
 
592
- if (id == rb_intern("mode"))
593
- options->mode = NUM2INT(value);
594
- else if (id == rb_intern("maxreaders"))
595
- options->maxreaders = NUM2INT(value);
596
- else if (id == rb_intern("maxdbs"))
597
- options->maxdbs = NUM2INT(value);
598
- else if (id == rb_intern("mapsize"))
599
- options->mapsize = NUM2SSIZET(value);
910
+ if (id == rb_intern("mode"))
911
+ options->mode = NUM2INT(value);
912
+ else if (id == rb_intern("maxreaders"))
913
+ options->maxreaders = NUM2INT(value);
914
+ else if (id == rb_intern("maxdbs"))
915
+ options->maxdbs = NUM2INT(value);
916
+ else if (id == rb_intern("mapsize"))
917
+ options->mapsize = NUM2SSIZET(value);
600
918
 
601
919
  #define FLAG(const, name) else if (id == rb_intern(#name)) { if (RTEST(value)) { options->flags |= MDB_##const; } }
602
920
  #include "env_flags.h"
603
921
  #undef FLAG
604
922
 
605
- else {
606
- VALUE s = rb_inspect(key);
607
- rb_raise(cError, "Invalid option %s", StringValueCStr(s));
608
- }
923
+ else {
924
+ VALUE s = rb_inspect(key);
925
+ rb_raise(cError, "Invalid option %s", StringValueCStr(s));
926
+ }
609
927
 
610
- return 0;
928
+ return 0;
611
929
  }
612
930
 
613
931
  /**
@@ -645,50 +963,52 @@ static int environment_options(VALUE key, VALUE value, EnvironmentOptions* optio
645
963
  * end
646
964
  */
647
965
  static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
648
- VALUE path, option_hash;
966
+ VALUE path, option_hash;
649
967
 
650
968
  #ifdef RB_SCAN_ARGS_KEYWORDS
651
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
652
- argc, argv, "1:", &path, &option_hash);
969
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
970
+ argc, argv, "1:", &path, &option_hash);
653
971
  #else
654
- rb_scan_args(argc, argv, "1:", &path, &option_hash);
972
+ rb_scan_args(argc, argv, "1:", &path, &option_hash);
655
973
  #endif
656
974
 
657
- EnvironmentOptions options = {
658
- .flags = MDB_NOTLS,
659
- .maxreaders = -1,
660
- .maxdbs = 128,
661
- .mapsize = 0,
662
- .mode = 0755,
663
- };
664
- if (!NIL_P(option_hash))
665
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))environment_options,
666
- (VALUE)&options);
667
-
668
- MDB_env* env;
669
- check(mdb_env_create(&env));
670
-
671
- Environment* environment;
672
- VALUE venv = Data_Make_Struct(cEnvironment, Environment, environment_mark,
673
- environment_free, environment);
674
- environment->env = env;
675
- environment->thread_txn_hash = rb_hash_new();
676
- environment->txn_thread_hash = rb_hash_new();
677
-
678
- if (options.maxreaders > 0)
679
- check(mdb_env_set_maxreaders(env, options.maxreaders));
680
- if (options.mapsize > 0)
681
- check(mdb_env_set_mapsize(env, options.mapsize));
682
-
683
- check(mdb_env_set_maxdbs(env, options.maxdbs <= 0 ? 1 : options.maxdbs));
684
- VALUE expanded_path = rb_file_expand_path(path, Qnil);
685
- check(mdb_env_open(env, StringValueCStr(expanded_path), options.flags,
686
- options.mode));
687
-
688
- if (rb_block_given_p())
689
- return rb_ensure(rb_yield, venv, environment_close, venv);
690
-
691
- return venv;
975
+ EnvironmentOptions options = {
976
+ // .flags = MDB_NOTLS,
977
+ .flags = 0,
978
+ .maxreaders = -1,
979
+ .maxdbs = 128,
980
+ .mapsize = 0,
981
+ .mode = 0755,
982
+ };
983
+ if (!NIL_P(option_hash))
984
+ rb_hash_foreach(option_hash, (int (*)(ANYARGS))environment_options,
985
+ (VALUE)&options);
986
+
987
+ MDB_env* env;
988
+ check(mdb_env_create(&env));
989
+
990
+ Environment* environment;
991
+ VALUE venv = TypedData_Make_Struct(cEnvironment, Environment,
992
+ &lmdb_environment_type, environment);
993
+ environment->env = env;
994
+ environment->thread_txn_hash = rb_hash_new();
995
+ environment->txn_thread_hash = rb_hash_new();
996
+ environment->rw_txn_thread = Qnil;
997
+
998
+ if (options.maxreaders > 0)
999
+ check(mdb_env_set_maxreaders(env, options.maxreaders));
1000
+ if (options.mapsize > 0)
1001
+ check(mdb_env_set_mapsize(env, options.mapsize));
1002
+
1003
+ check(mdb_env_set_maxdbs(env, options.maxdbs <= 0 ? 1 : options.maxdbs));
1004
+ VALUE expanded_path = rb_file_expand_path(path, Qnil);
1005
+ check(mdb_env_open(env, StringValueCStr(expanded_path), options.flags,
1006
+ options.mode));
1007
+
1008
+ if (rb_block_given_p())
1009
+ return rb_ensure(rb_yield, venv, environment_close, venv);
1010
+
1011
+ return venv;
692
1012
  }
693
1013
 
694
1014
  /**
@@ -709,16 +1029,16 @@ static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
709
1029
  * env.flags #=> [:writemap, :nometasync]
710
1030
  */
711
1031
  static VALUE environment_flags(VALUE self) {
712
- unsigned int flags;
713
- ENVIRONMENT(self, environment);
714
- check(mdb_env_get_flags(environment->env, &flags));
1032
+ unsigned int flags;
1033
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1034
+ check(mdb_env_get_flags(environment->env, &flags));
715
1035
 
716
- VALUE ret = rb_ary_new();
1036
+ VALUE ret = rb_ary_new();
717
1037
  #define FLAG(const, name) if (flags & MDB_##const) rb_ary_push(ret, ID2SYM(rb_intern(#name)));
718
1038
  #include "env_flags.h"
719
1039
  #undef FLAG
720
1040
 
721
- return ret;
1041
+ return ret;
722
1042
  }
723
1043
 
724
1044
  /**
@@ -727,33 +1047,33 @@ static VALUE environment_flags(VALUE self) {
727
1047
  * @return [String] the path that was used to open the environment.
728
1048
  */
729
1049
  static VALUE environment_path(VALUE self) {
730
- const char* path;
731
- ENVIRONMENT(self, environment);
732
- check(mdb_env_get_path(environment->env, &path));
733
- return rb_str_new2(path);
1050
+ const char* path;
1051
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1052
+ check(mdb_env_get_path(environment->env, &path));
1053
+ return rb_str_new2(path);
734
1054
  }
735
1055
 
736
1056
  static VALUE environment_set_mapsize(VALUE self, VALUE size) {
737
- ENVIRONMENT(self, environment);
738
- check(mdb_env_set_mapsize(environment->env, NUM2LONG(size)));
739
- return Qnil;
1057
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1058
+ check(mdb_env_set_mapsize(environment->env, NUM2LONG(size)));
1059
+ return Qnil;
740
1060
  }
741
1061
 
742
1062
  static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set) {
743
- ENVIRONMENT(self, environment);
1063
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
744
1064
 
745
- int i;
746
- for (i = 0; i < argc; ++i) {
747
- ID id = rb_to_id(argv[i]);
1065
+ int i;
1066
+ for (i = 0; i < argc; ++i) {
1067
+ ID id = rb_to_id(argv[i]);
748
1068
 
749
- if (0) {}
1069
+ if (0) {}
750
1070
  #define FLAG(const, name) else if (id == rb_intern(#name)) check(mdb_env_set_flags(environment->env, MDB_##const, set));
751
1071
  #include "env_flags.h"
752
1072
  #undef FLAG
753
- else
754
- rb_raise(cError, "Invalid option %s", StringValueCStr(argv[i]));
755
- }
756
- return Qnil;
1073
+ else
1074
+ rb_raise(cError, "Invalid option %s", StringValueCStr(argv[i]));
1075
+ }
1076
+ return Qnil;
757
1077
  }
758
1078
 
759
1079
  /**
@@ -767,8 +1087,8 @@ static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set
767
1087
  * env.set_flags :nosync, :writemap
768
1088
  */
769
1089
  static VALUE environment_set_flags(int argc, VALUE* argv, VALUE self) {
770
- environment_change_flags(argc, argv, self, 1);
771
- return Qnil;
1090
+ environment_change_flags(argc, argv, self, 1);
1091
+ return Qnil;
772
1092
  }
773
1093
 
774
1094
  /**
@@ -782,8 +1102,8 @@ static VALUE environment_set_flags(int argc, VALUE* argv, VALUE self) {
782
1102
  * env.clear_flags :nosync, :writemap
783
1103
  */
784
1104
  static VALUE environment_clear_flags(int argc, VALUE* argv, VALUE self) {
785
- environment_change_flags(argc, argv, self, 0);
786
- return Qnil;
1105
+ environment_change_flags(argc, argv, self, 0);
1106
+ return Qnil;
787
1107
  }
788
1108
 
789
1109
  /**
@@ -796,32 +1116,57 @@ static VALUE environment_clear_flags(int argc, VALUE* argv, VALUE self) {
796
1116
  * end
797
1117
  */
798
1118
  static VALUE environment_active_txn(VALUE self) {
799
- ENVIRONMENT(self, environment);
800
- return rb_hash_aref(environment->thread_txn_hash, rb_thread_current());
1119
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1120
+ return rb_hash_aref(environment->thread_txn_hash, rb_thread_current());
801
1121
  }
802
1122
 
803
1123
  static void environment_set_active_txn(VALUE self, VALUE thread, VALUE txn) {
804
- ENVIRONMENT(self, environment);
805
-
806
- if (NIL_P(txn)) {
807
- VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
808
- if (!NIL_P(oldtxn)) {
809
- rb_hash_delete(environment->thread_txn_hash, thread);
810
- rb_hash_delete(environment->txn_thread_hash, oldtxn);
811
- }
812
- } else {
813
- VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
814
- if (!NIL_P(oldtxn)) {
815
- rb_hash_delete(environment->txn_thread_hash, oldtxn);
816
- }
817
- rb_hash_aset(environment->txn_thread_hash, txn, thread);
818
- rb_hash_aset(environment->thread_txn_hash, thread, txn);
1124
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1125
+
1126
+ VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
1127
+
1128
+ if (NIL_P(txn)) {
1129
+ // we are clearing out whatever is there
1130
+ if (REXISTS(oldtxn)) {
1131
+ rb_hash_delete(environment->thread_txn_hash, thread);
1132
+ rb_hash_delete(environment->txn_thread_hash, oldtxn);
1133
+
1134
+ TRANSACTION(oldtxn, &lmdb_transaction_type, transaction);
1135
+ if (!REXISTS(transaction->parent) && !(transaction->flags & MDB_RDONLY)) {
1136
+ if (REXISTS(environment->rw_txn_thread)) {
1137
+ if (environment->rw_txn_thread == thread)
1138
+ environment->rw_txn_thread = Qnil;
1139
+ else
1140
+ rb_raise(cError, "INTERNAL: rw_txn_thread %p != %p",
1141
+ (void *)environment->rw_txn_thread, (void *)thread);
819
1142
  }
1143
+ else rb_raise(cError, "INTERNAL: rw_txn_thread cleared out of band");
1144
+ }
1145
+ }
1146
+ } else {
1147
+ // we are setting/replacing the thread's current transaction
1148
+
1149
+ if (REXISTS(oldtxn))
1150
+ rb_hash_delete(environment->txn_thread_hash, oldtxn);
1151
+
1152
+ rb_hash_aset(environment->txn_thread_hash, txn, thread);
1153
+ rb_hash_aset(environment->thread_txn_hash, thread, txn);
1154
+
1155
+ TRANSACTION(txn, &lmdb_transaction_type, transaction);
1156
+ if (!REXISTS(transaction->parent) && !(transaction->flags & MDB_RDONLY)) {
1157
+ if (REXISTS(environment->rw_txn_thread)) {
1158
+ if (environment->rw_txn_thread != thread)
1159
+ rb_raise(cError, "Can't nest a transaction on another thread");
1160
+ }
1161
+
1162
+ environment->rw_txn_thread = thread;
1163
+ }
1164
+ }
820
1165
  }
821
1166
 
822
1167
  static MDB_txn* extract_txn(VALUE vtxn) {
823
1168
  if (NIL_P(vtxn)) return NULL;
824
- TRANSACTION(vtxn, transaction);
1169
+ TRANSACTION(vtxn, &lmdb_transaction_type, transaction);
825
1170
  if (!transaction->txn) rb_raise(cError, "Transaction is already terminated");
826
1171
  if (transaction->thread != rb_thread_current())
827
1172
  rb_raise(cError, "Transaction is from another thread");
@@ -829,15 +1174,15 @@ static MDB_txn* extract_txn(VALUE vtxn) {
829
1174
  }
830
1175
 
831
1176
  static MDB_txn* active_txn(VALUE self) {
832
- VALUE vtxn = environment_active_txn(self);
833
- return extract_txn(vtxn);
1177
+ VALUE vtxn = environment_active_txn(self);
1178
+ return extract_txn(vtxn);
834
1179
  }
835
1180
 
836
1181
  static MDB_txn* need_txn(VALUE self) {
837
- MDB_txn* txn = active_txn(self);
1182
+ MDB_txn* txn = active_txn(self);
838
1183
 
839
- if (!txn) rb_raise(cError, "No active transaction");
840
- return txn;
1184
+ if (!txn) rb_raise(cError, "No active transaction");
1185
+ return txn;
841
1186
  }
842
1187
 
843
1188
  /**
@@ -846,9 +1191,14 @@ static MDB_txn* need_txn(VALUE self) {
846
1191
  * transaction. A transaction commits when it exits the block successfully.
847
1192
  * A transaction aborts when it raises an exception or calls
848
1193
  * {Transaction#abort}.
1194
+ *
849
1195
  * @param [Boolean] readonly This transaction will not perform any
850
1196
  * write operations
1197
+ *
851
1198
  * @note Transactions can be nested.
1199
+ * @note do a `break txn.commit` if you want to exit from the block
1200
+ * early without aborting.
1201
+ *
852
1202
  * @yield [txn] The block to be executed with the body of the transaction.
853
1203
  * @yieldparam txn [Transaction] An optional transaction argument
854
1204
  * @example
@@ -869,18 +1219,58 @@ static MDB_txn* need_txn(VALUE self) {
869
1219
  * db['a'] #=> 3
870
1220
  */
871
1221
  static VALUE environment_transaction(int argc, VALUE *argv, VALUE self) {
872
- rb_need_block();
1222
+ rb_need_block();
873
1223
 
874
- VALUE readonly;
875
- rb_scan_args(argc, argv, "01", &readonly);
876
- unsigned int flags = RTEST(readonly) ? MDB_RDONLY : 0;
1224
+ VALUE readonly;
1225
+ rb_scan_args(argc, argv, "01", &readonly);
1226
+ unsigned int flags = RTEST(readonly) ? MDB_RDONLY : 0;
1227
+
1228
+ return with_transaction(self, rb_yield, Qnil, flags);
1229
+ }
1230
+
1231
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
1232
+ static void database_mark(void *ptr) {
1233
+ Database *database = (Database *)ptr;
1234
+ if (database)
1235
+ GC_MARK_MOVABLE(database->env);
1236
+ }
1237
+
1238
+ static void database_compact(void *ptr) {
1239
+ Database *database = (Database *)ptr;
1240
+ if (database)
1241
+ GC_LOCATION(database->env);
1242
+ }
877
1243
 
878
- return with_transaction(self, rb_yield, Qnil, flags);
1244
+ static void database_free(void* ptr) {
1245
+ Database *database = (Database *)ptr;
1246
+ xfree(database);
879
1247
  }
880
1248
 
881
- static void database_mark(Database* database) {
882
- rb_gc_mark(database->env);
1249
+ /* Define the modern TypedData specifications */
1250
+ static const rb_data_type_t lmdb_database_type = {
1251
+ .wrap_struct_name = "LMDB::Database",
1252
+ .function = {
1253
+ .dmark = database_mark,
1254
+ .dfree = database_free,
1255
+ .dsize = NULL,
1256
+ .dcompact = database_compact,
1257
+ },
1258
+ .flags = 0
1259
+ };
1260
+
1261
+ /*
1262
+ static VALUE database_compact_m(VALUE self) {
1263
+ DATABASE(self, database);
1264
+ GC_LOCATION(database->env);
1265
+ return Qnil;
1266
+ }
1267
+ */
1268
+ #else
1269
+ static void database_mark(void* ptr) {
1270
+ Database *database = (Database *)ptr;
1271
+ rb_gc_mark(database->env);
883
1272
  }
1273
+ #endif
884
1274
 
885
1275
  #define METHOD database_flags
886
1276
  #define FILE "dbi_flags.h"
@@ -899,7 +1289,7 @@ static void database_mark(Database* database) {
899
1289
  * database aborts, the database is not created.
900
1290
  * @return [Database] newly-opened database
901
1291
  * @raise [Error] if there is an error opening the database
902
- * @param [String] name Optional name for the database to be opened.
1292
+ * @param [String, Symbol, nil] name Optional name for the database to be opened.
903
1293
  * @param [Hash] options Options for the database.
904
1294
  * @option options [Boolean] :reversekey Keys are strings to be
905
1295
  * compared in reverse order, from the end of the strings to the
@@ -926,35 +1316,64 @@ static void database_mark(Database* database) {
926
1316
  * transaction or a read-only environment.
927
1317
  */
928
1318
  static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
929
- ENVIRONMENT(self, environment);
930
- if (!active_txn(self))
931
- return call_with_transaction(self, self, "database", argc, argv, 0);
1319
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
932
1320
 
933
- VALUE name, option_hash;
934
- #ifdef RB_SCAN_ARGS_KEYWORDS
935
- rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS,
936
- argc, argv, "01:", &name, &option_hash);
937
- #else
938
- rb_scan_args(argc, argv, "01:", &name, &option_hash);
939
- #endif
1321
+ if (!active_txn(self))
1322
+ return call_with_transaction(self, self, "database", argc, argv, 0);
940
1323
 
1324
+ VALUE name = Qnil;
1325
+ VALUE option_hash = Qnil;
941
1326
 
942
- int flags = 0;
943
- if (!NIL_P(option_hash))
944
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_flags,
945
- (VALUE)&flags);
1327
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1328
+ argc, argv, "01:", &name, &option_hash);
946
1329
 
947
- MDB_dbi dbi;
948
- check(mdb_dbi_open(need_txn(self), NIL_P(name) ? 0 : StringValueCStr(name),
949
- flags, &dbi));
1330
+ int flags = 0;
1331
+ if (!NIL_P(option_hash))
1332
+ rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_flags,
1333
+ (VALUE)&flags);
950
1334
 
951
- Database* database;
952
- VALUE vdb = Data_Make_Struct(cDatabase, Database, database_mark, free,
953
- database);
954
- database->dbi = dbi;
955
- database->env = self;
1335
+ MDB_dbi dbi;
956
1336
 
957
- return vdb;
1337
+ if (name && !NIL_P(name)) {
1338
+ if (RB_TYPE_P(name, T_SYMBOL))
1339
+ name = rb_sym2str(name);
1340
+
1341
+ Check_Type(name, T_STRING);
1342
+ }
1343
+
1344
+ check(mdb_dbi_open(need_txn(self), NIL_P(name) ? 0 : StringValueCStr(name),
1345
+ flags, &dbi));
1346
+
1347
+ Database* database;
1348
+ VALUE vdb = TypedData_Make_Struct(cDatabase, Database,
1349
+ &lmdb_database_type, database);
1350
+ database->dbi = dbi;
1351
+ database->env = self;
1352
+
1353
+ return vdb;
1354
+ }
1355
+
1356
+ /**
1357
+ * @overload []()
1358
+ * Accesses an existing database. Will raise if it doesn't exist.
1359
+ *
1360
+ * @param name [String, Symbol, nil] name for the database to be opened.
1361
+ *
1362
+ * @raise [Error] if there is an error opening the database
1363
+ *
1364
+ * @return [Database] a database handle
1365
+ */
1366
+
1367
+ static VALUE environment_database_aref(VALUE self, VALUE name) {
1368
+ // If they pass nil explicitly, treat it as zero arguments (anonymous database)
1369
+ int argc = NIL_P(name) ? 0 : 1;
1370
+
1371
+ // Package it up into an argv array
1372
+ VALUE argv[1];
1373
+ argv[0] = name;
1374
+
1375
+ // Call your existing function directly
1376
+ return environment_database(argc, argv, self);
958
1377
  }
959
1378
 
960
1379
  /**
@@ -966,42 +1385,42 @@ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
966
1385
  * @raise [Error] If there is an error opening the main database.
967
1386
  */
968
1387
  static VALUE environment_databases(VALUE self) {
969
- ENVIRONMENT(self, environment);
970
- if (!active_txn(self))
971
- return call_with_transaction(self, self, "databases", 0, 0, MDB_RDONLY);
972
-
973
- MDB_dbi dbi;
974
- MDB_cursor *cursor;
975
- MDB_txn *txn = need_txn(self);
976
- MDB_val key;
977
-
978
- check(mdb_dbi_open(txn, NULL, 0, &dbi));
979
- check(mdb_cursor_open(txn, dbi, &cursor));
980
-
981
- VALUE ret = rb_ary_new();
982
- while (mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP) == MDB_SUCCESS) {
983
- char *intern_db_name;
984
- MDB_dbi db;
985
- VALUE db_name;
986
-
987
- if (memchr(key.mv_data, '\0', key.mv_size))
988
- continue;
989
-
990
- intern_db_name = malloc(key.mv_size + 1);
991
- memcpy(intern_db_name, key.mv_data, key.mv_size);
992
- intern_db_name[key.mv_size] = '\0';
993
-
994
- if (mdb_dbi_open(txn, intern_db_name, 0, &db) == MDB_SUCCESS) {
995
- mdb_dbi_close(environment->env, db);
996
- db_name = rb_str_new(key.mv_data, key.mv_size);
997
- rb_ary_push(ret, db_name);
998
- }
999
- free(intern_db_name);
1388
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1389
+ if (!active_txn(self))
1390
+ return call_with_transaction(self, self, "databases", 0, 0, MDB_RDONLY);
1391
+
1392
+ MDB_dbi dbi;
1393
+ MDB_cursor *cursor;
1394
+ MDB_txn *txn = need_txn(self);
1395
+ MDB_val key;
1396
+
1397
+ check(mdb_dbi_open(txn, NULL, 0, &dbi));
1398
+ check(mdb_cursor_open(txn, dbi, &cursor));
1399
+
1400
+ VALUE ret = rb_ary_new();
1401
+ while (mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP) == MDB_SUCCESS) {
1402
+ char *intern_db_name;
1403
+ MDB_dbi db;
1404
+ VALUE db_name;
1405
+
1406
+ if (memchr(key.mv_data, '\0', key.mv_size))
1407
+ continue;
1408
+
1409
+ intern_db_name = malloc(key.mv_size + 1);
1410
+ memcpy(intern_db_name, key.mv_data, key.mv_size);
1411
+ intern_db_name[key.mv_size] = '\0';
1412
+
1413
+ if (mdb_dbi_open(txn, intern_db_name, 0, &db) == MDB_SUCCESS) {
1414
+ mdb_dbi_close(environment->env, db);
1415
+ db_name = rb_str_new(key.mv_data, key.mv_size);
1416
+ rb_ary_push(ret, db_name);
1000
1417
  }
1418
+ xfree(intern_db_name);
1419
+ }
1001
1420
 
1002
- mdb_cursor_close(cursor);
1421
+ mdb_cursor_close(cursor);
1003
1422
 
1004
- return ret;
1423
+ return ret;
1005
1424
  }
1006
1425
 
1007
1426
  /**
@@ -1016,14 +1435,14 @@ static VALUE environment_databases(VALUE self) {
1016
1435
  * * +:entries+ Number of data items
1017
1436
  */
1018
1437
  static VALUE database_stat(VALUE self) {
1019
- DATABASE(self, database);
1020
- if (!active_txn(database->env))
1021
- return call_with_transaction(database->env,
1022
- self, "stat", 0, 0, MDB_RDONLY);
1438
+ DATABASE(self, &lmdb_database_type, database);
1439
+ if (!active_txn(database->env))
1440
+ return call_with_transaction(database->env,
1441
+ self, "stat", 0, 0, MDB_RDONLY);
1023
1442
 
1024
- MDB_stat stat;
1025
- check(mdb_stat(need_txn(database->env), database->dbi, &stat));
1026
- return stat2hash(&stat);
1443
+ MDB_stat stat;
1444
+ check(mdb_stat(need_txn(database->env), database->dbi, &stat));
1445
+ return stat2hash(&stat);
1027
1446
  }
1028
1447
 
1029
1448
  /**
@@ -1032,13 +1451,13 @@ static VALUE database_stat(VALUE self) {
1032
1451
  * @return [Hash] The flags.
1033
1452
  */
1034
1453
  static VALUE database_get_flags(VALUE self) {
1035
- DATABASE(self, database);
1036
- if (!active_txn(database->env))
1037
- return call_with_transaction(database->env,
1038
- self, "flags", 0, 0, MDB_RDONLY);
1039
- unsigned int flags;
1040
- check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1041
- return flags2hash(flags);
1454
+ DATABASE(self, &lmdb_database_type, database);
1455
+ if (!active_txn(database->env))
1456
+ return call_with_transaction(database->env,
1457
+ self, "flags", 0, 0, MDB_RDONLY);
1458
+ unsigned int flags;
1459
+ check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1460
+ return flags2hash(flags);
1042
1461
  }
1043
1462
 
1044
1463
  /* XXX these two could probably also be macro'd, or maybe not i dunno */
@@ -1049,14 +1468,14 @@ static VALUE database_get_flags(VALUE self) {
1049
1468
  * @return [true, false]
1050
1469
  */
1051
1470
  static VALUE database_is_dupsort(VALUE self) {
1052
- DATABASE(self, database);
1053
- if (!active_txn(database->env))
1054
- return call_with_transaction(database->env, self,
1055
- "dupsort?", 0, 0, MDB_RDONLY);
1056
- unsigned int flags;
1057
- check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1471
+ DATABASE(self, &lmdb_database_type, database);
1472
+ if (!active_txn(database->env))
1473
+ return call_with_transaction(database->env, self,
1474
+ "dupsort?", 0, 0, MDB_RDONLY);
1475
+ unsigned int flags;
1476
+ check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1058
1477
 
1059
- return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
1478
+ return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
1060
1479
  }
1061
1480
 
1062
1481
  /**
@@ -1065,14 +1484,14 @@ static VALUE database_is_dupsort(VALUE self) {
1065
1484
  * @return [true, false]
1066
1485
  */
1067
1486
  static VALUE database_is_dupfixed(VALUE self) {
1068
- DATABASE(self, database);
1069
- if (!active_txn(database->env))
1070
- return call_with_transaction(database->env, self,
1071
- "dupfixed?", 0, 0, MDB_RDONLY);
1072
- unsigned int flags;
1073
- check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1487
+ DATABASE(self, &lmdb_database_type, database);
1488
+ if (!active_txn(database->env))
1489
+ return call_with_transaction(database->env, self,
1490
+ "dupfixed?", 0, 0, MDB_RDONLY);
1491
+ unsigned int flags;
1492
+ check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1074
1493
 
1075
- return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
1494
+ return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
1076
1495
  }
1077
1496
 
1078
1497
  /**
@@ -1082,11 +1501,11 @@ static VALUE database_is_dupfixed(VALUE self) {
1082
1501
  * @note The drop happens transactionally.
1083
1502
  */
1084
1503
  static VALUE database_drop(VALUE self) {
1085
- DATABASE(self, database);
1086
- if (!active_txn(database->env))
1087
- return call_with_transaction(database->env, self, "drop", 0, 0, 0);
1088
- check(mdb_drop(need_txn(database->env), database->dbi, 1));
1089
- return Qnil;
1504
+ DATABASE(self, &lmdb_database_type, database);
1505
+ if (!active_txn(database->env))
1506
+ return call_with_transaction(database->env, self, "drop", 0, 0, 0);
1507
+ check(mdb_drop(need_txn(database->env), database->dbi, 1));
1508
+ return Qnil;
1090
1509
  }
1091
1510
 
1092
1511
  /**
@@ -1096,11 +1515,11 @@ static VALUE database_drop(VALUE self) {
1096
1515
  * @note The clear happens transactionally.
1097
1516
  */
1098
1517
  static VALUE database_clear(VALUE self) {
1099
- DATABASE(self, database);
1100
- if (!active_txn(database->env))
1101
- return call_with_transaction(database->env, self, "clear", 0, 0, 0);
1102
- check(mdb_drop(need_txn(database->env), database->dbi, 0));
1103
- return Qnil;
1518
+ DATABASE(self, &lmdb_database_type, database);
1519
+ if (!active_txn(database->env))
1520
+ return call_with_transaction(database->env, self, "clear", 0, 0, 0);
1521
+ check(mdb_drop(need_txn(database->env), database->dbi, 0));
1522
+ return Qnil;
1104
1523
  }
1105
1524
 
1106
1525
  /**
@@ -1113,20 +1532,23 @@ static VALUE database_clear(VALUE self) {
1113
1532
  * @param key The key of the record to retrieve.
1114
1533
  */
1115
1534
  static VALUE database_get(VALUE self, VALUE vkey) {
1116
- DATABASE(self, database);
1117
- if (!active_txn(database->env))
1118
- return call_with_transaction(database->env, self, "get", 1, &vkey, MDB_RDONLY);
1535
+ DATABASE(self, &lmdb_database_type, database);
1536
+ if (!active_txn(database->env))
1537
+ return call_with_transaction(database->env, self, "get", 1, &vkey, MDB_RDONLY);
1538
+
1539
+
1540
+ vkey = StringValue(vkey);
1541
+ MDB_val key, value;
1542
+ key.mv_size = RSTRING_LEN(vkey);
1543
+ key.mv_data = RSTRING_PTR(vkey);
1119
1544
 
1120
- vkey = StringValue(vkey);
1121
- MDB_val key, value;
1122
- key.mv_size = RSTRING_LEN(vkey);
1123
- key.mv_data = RSTRING_PTR(vkey);
1545
+ // rb_warn("lol database get %s", (char *)key.mv_data);
1124
1546
 
1125
- int ret = mdb_get(need_txn(database->env), database->dbi, &key, &value);
1126
- if (ret == MDB_NOTFOUND)
1127
- return Qnil;
1128
- check(ret);
1129
- return rb_str_new(value.mv_data, value.mv_size);
1547
+ int ret = mdb_get(need_txn(database->env), database->dbi, &key, &value);
1548
+ if (ret == MDB_NOTFOUND)
1549
+ return Qnil;
1550
+ check(ret);
1551
+ return rb_str_new(value.mv_data, value.mv_size);
1130
1552
  }
1131
1553
 
1132
1554
  #define METHOD database_put_flags
@@ -1163,34 +1585,34 @@ static VALUE database_get(VALUE self, VALUE vkey) {
1163
1585
  * data.
1164
1586
  */
1165
1587
  static VALUE database_put(int argc, VALUE *argv, VALUE self) {
1166
- DATABASE(self, database);
1167
- if (!active_txn(database->env))
1168
- return call_with_transaction(database->env, self, "put", argc, argv, 0);
1588
+ DATABASE(self, &lmdb_database_type, database);
1589
+ if (!active_txn(database->env))
1590
+ return call_with_transaction(database->env, self, "put", argc, argv, 0);
1169
1591
 
1170
- VALUE vkey, vval, option_hash = Qnil;
1592
+ VALUE vkey, vval, option_hash = Qnil;
1171
1593
  #ifdef RB_SCAN_ARGS_KEYWORDS
1172
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1173
- argc, argv, "20:", &vkey, &vval, &option_hash);
1594
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1595
+ argc, argv, "20:", &vkey, &vval, &option_hash);
1174
1596
  #else
1175
- rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
1597
+ rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
1176
1598
  #endif
1177
1599
 
1178
- int flags = 0;
1179
- if (!NIL_P(option_hash))
1180
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_put_flags,
1181
- (VALUE)&flags);
1600
+ int flags = 0;
1601
+ if (!NIL_P(option_hash))
1602
+ rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_put_flags,
1603
+ (VALUE)&flags);
1182
1604
 
1183
- vkey = StringValue(vkey);
1184
- vval = StringValue(vval);
1605
+ vkey = StringValue(vkey);
1606
+ vval = StringValue(vval);
1185
1607
 
1186
- MDB_val key, value;
1187
- key.mv_size = RSTRING_LEN(vkey);
1188
- key.mv_data = RSTRING_PTR(vkey);
1189
- value.mv_size = RSTRING_LEN(vval);
1190
- value.mv_data = RSTRING_PTR(vval);
1608
+ MDB_val key, value;
1609
+ key.mv_size = RSTRING_LEN(vkey);
1610
+ key.mv_data = RSTRING_PTR(vkey);
1611
+ value.mv_size = RSTRING_LEN(vval);
1612
+ value.mv_data = RSTRING_PTR(vval);
1191
1613
 
1192
- check(mdb_put(need_txn(database->env), database->dbi, &key, &value, flags));
1193
- return Qnil;
1614
+ check(mdb_put(need_txn(database->env), database->dbi, &key, &value, flags));
1615
+ return Qnil;
1194
1616
  }
1195
1617
 
1196
1618
  /**
@@ -1209,59 +1631,97 @@ static VALUE database_put(int argc, VALUE *argv, VALUE self) {
1209
1631
  * @raise [Error] if the specified key/value pair is not in the database.
1210
1632
  */
1211
1633
  static VALUE database_delete(int argc, VALUE *argv, VALUE self) {
1212
- DATABASE(self, database);
1213
- if (!active_txn(database->env))
1214
- return call_with_transaction(database->env, self, "delete", argc, argv, 0);
1215
-
1216
- VALUE vkey, vval;
1217
- rb_scan_args(argc, argv, "11", &vkey, &vval);
1218
-
1219
- vkey = StringValue(vkey);
1220
-
1221
- MDB_val key;
1222
- key.mv_size = RSTRING_LEN(vkey);
1223
- key.mv_data = RSTRING_PTR(vkey);
1224
-
1225
- if (NIL_P(vval)) {
1226
- check(mdb_del(need_txn(database->env), database->dbi, &key, 0));
1227
- } else {
1228
- vval = StringValue(vval);
1229
- MDB_val value;
1230
- value.mv_size = RSTRING_LEN(vval);
1231
- value.mv_data = RSTRING_PTR(vval);
1232
- check(mdb_del(need_txn(database->env), database->dbi, &key, &value));
1233
- }
1634
+ DATABASE(self, &lmdb_database_type, database);
1635
+ if (!active_txn(database->env))
1636
+ return call_with_transaction(database->env, self, "delete", argc, argv, 0);
1637
+
1638
+ VALUE vkey, vval;
1639
+ rb_scan_args(argc, argv, "11", &vkey, &vval);
1640
+
1641
+ vkey = StringValue(vkey);
1642
+
1643
+ MDB_val key;
1644
+ key.mv_size = RSTRING_LEN(vkey);
1645
+ key.mv_data = RSTRING_PTR(vkey);
1234
1646
 
1235
- return Qnil;
1647
+ if (NIL_P(vval)) {
1648
+ check(mdb_del(need_txn(database->env), database->dbi, &key, 0));
1649
+ } else {
1650
+ vval = StringValue(vval);
1651
+ MDB_val value;
1652
+ value.mv_size = RSTRING_LEN(vval);
1653
+ value.mv_data = RSTRING_PTR(vval);
1654
+ check(mdb_del(need_txn(database->env), database->dbi, &key, &value));
1655
+ }
1656
+
1657
+ return Qnil;
1236
1658
  }
1237
1659
 
1238
- static void cursor_free(Cursor* cursor) {
1239
- if (cursor->cur) {
1240
- rb_warn("Memory leak - Garbage collecting open cursor");
1241
- // mdb_cursor_close(cursor->cur);
1242
- }
1660
+ static void cursor_free(void* ptr) {
1661
+ Cursor *cursor = (Cursor *)ptr;
1662
+ if (cursor->cur) {
1663
+ rb_warn("Memory leak - Garbage collecting open cursor");
1664
+ mdb_cursor_close(cursor->cur);
1665
+ }
1243
1666
 
1244
- free(cursor);
1667
+ xfree(cursor);
1245
1668
  }
1246
1669
 
1247
1670
  static void cursor_check(Cursor* cursor) {
1248
- if (!cursor->cur)
1249
- rb_raise(cError, "Cursor is closed");
1250
- }
1671
+ if (!cursor->cur)
1672
+ rb_raise(cError, "Cursor is closed");
1673
+ }
1674
+
1675
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
1676
+ static void cursor_mark(void *ptr) {
1677
+ Cursor *cursor = (Cursor *)ptr;
1678
+ if (cursor)
1679
+ GC_MARK_MOVABLE(cursor->db);
1680
+ }
1681
+
1682
+ static void cursor_compact(void *ptr) {
1683
+ Cursor *cursor = (Cursor *)ptr;
1684
+ if (cursor)
1685
+ GC_LOCATION(cursor->db);
1686
+ }
1687
+
1688
+ /* Define the modern TypedData specifications */
1689
+ static const rb_data_type_t lmdb_cursor_type = {
1690
+ .wrap_struct_name = "LMDB::Cursor",
1691
+ .function = {
1692
+ .dmark = cursor_mark,
1693
+ .dfree = cursor_free,
1694
+ .dsize = NULL,
1695
+ .dcompact = cursor_compact,
1696
+ },
1697
+ .flags = 0
1698
+ };
1699
+
1700
+ /*
1701
+ static VALUE cursor_compact_m(VALUE self) {
1702
+ CURSOR(self, cursor);
1703
+ cursor_compact(cursor);
1704
+ return Qnil;
1705
+ }
1706
+ */
1251
1707
 
1708
+ #else
1252
1709
  static void cursor_mark(Cursor* cursor) {
1253
- rb_gc_mark(cursor->db);
1710
+ rb_gc_mark(cursor->db);
1254
1711
  }
1712
+ #endif
1255
1713
 
1256
1714
  /**
1257
1715
  * @overload close
1258
1716
  * Close a cursor. The cursor must not be used again after this call.
1259
1717
  */
1260
1718
  static VALUE cursor_close(VALUE self) {
1261
- CURSOR(self, cursor);
1262
- mdb_cursor_close(cursor->cur);
1263
- cursor->cur = 0;
1264
- return Qnil;
1719
+ CURSOR_NOCHECK(self, &lmdb_cursor_type, cursor);
1720
+ if (cursor->cur) {
1721
+ mdb_cursor_close(cursor->cur);
1722
+ cursor->cur = 0;
1723
+ }
1724
+ return Qnil;
1265
1725
  }
1266
1726
 
1267
1727
  /**
@@ -1282,44 +1742,45 @@ static VALUE cursor_close(VALUE self) {
1282
1742
  * end
1283
1743
  */
1284
1744
  static VALUE database_cursor(VALUE self) {
1285
- DATABASE(self, database);
1286
- if (!active_txn(database->env)) {
1287
- if (!rb_block_given_p()) {
1288
- rb_raise(cError, "Must call with block or active transaction.");
1289
- }
1290
- return call_with_transaction(database->env, self, "cursor", 0, 0, 0);
1291
- }
1745
+ DATABASE(self, &lmdb_database_type, database);
1746
+ if (!active_txn(database->env)) {
1747
+ if (!rb_block_given_p()) {
1748
+ rb_raise(cError, "Must call with block or active transaction.");
1749
+ }
1750
+ return call_with_transaction(database->env, self, "cursor", 0, 0, 0);
1751
+ }
1292
1752
 
1293
- MDB_cursor* cur;
1294
- check(mdb_cursor_open(need_txn(database->env), database->dbi, &cur));
1295
-
1296
- Cursor* cursor;
1297
- VALUE vcur = Data_Make_Struct(cCursor, Cursor, cursor_mark, cursor_free, cursor);
1298
- cursor->cur = cur;
1299
- cursor->db = self;
1300
-
1301
- if (rb_block_given_p()) {
1302
- int exception;
1303
- VALUE ret = rb_protect(rb_yield, vcur, &exception);
1304
- if (exception) {
1305
- cursor_close(vcur);
1306
- rb_jump_tag(exception);
1307
- }
1308
- cursor_close(vcur);
1309
- return ret;
1310
- }
1311
- else {
1312
- VALUE vtxn = environment_active_txn(database->env);
1313
- if (NIL_P(vtxn)) {
1314
- rb_fatal("Internal error: transaction finished unexpectedly.");
1315
- }
1316
- else {
1317
- TRANSACTION(vtxn, txn);
1318
- rb_ary_push(txn->cursors, vcur);
1319
- }
1320
- }
1753
+ MDB_cursor* cur;
1754
+ check(mdb_cursor_open(need_txn(database->env), database->dbi, &cur));
1321
1755
 
1322
- return vcur;
1756
+ Cursor* cursor;
1757
+ VALUE vcur = TypedData_Make_Struct(cCursor, Cursor,
1758
+ &lmdb_cursor_type, cursor);
1759
+ cursor->cur = cur;
1760
+ cursor->db = self;
1761
+
1762
+ if (rb_block_given_p()) {
1763
+ int exception;
1764
+ VALUE ret = rb_protect(rb_yield, vcur, &exception);
1765
+ if (exception) {
1766
+ cursor_close(vcur);
1767
+ rb_jump_tag(exception);
1768
+ }
1769
+ cursor_close(vcur);
1770
+ return ret;
1771
+ }
1772
+ else {
1773
+ VALUE vtxn = environment_active_txn(database->env);
1774
+ if (NIL_P(vtxn)) {
1775
+ rb_fatal("Internal error: transaction finished unexpectedly.");
1776
+ }
1777
+ else {
1778
+ TRANSACTION(vtxn, &lmdb_transaction_type, txn);
1779
+ rb_ary_push(txn->cursors, vcur);
1780
+ }
1781
+ }
1782
+
1783
+ return vcur;
1323
1784
  }
1324
1785
 
1325
1786
  /**
@@ -1327,8 +1788,8 @@ static VALUE database_cursor(VALUE self) {
1327
1788
  * @return [Environment] the environment to which this database belongs.
1328
1789
  */
1329
1790
  static VALUE database_env(VALUE self) {
1330
- DATABASE(self, database);
1331
- return database->env;
1791
+ DATABASE(self, &lmdb_database_type, database);
1792
+ return database->env;
1332
1793
  }
1333
1794
 
1334
1795
  /**
@@ -1339,11 +1800,11 @@ static VALUE database_env(VALUE self) {
1339
1800
  * nil if no record
1340
1801
  */
1341
1802
  static VALUE cursor_first(VALUE self) {
1342
- CURSOR(self, cursor);
1343
- MDB_val key, value;
1803
+ CURSOR(self, &lmdb_cursor_type, cursor);
1804
+ MDB_val key, value;
1344
1805
 
1345
- check(mdb_cursor_get(cursor->cur, &key, &value, MDB_FIRST));
1346
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1806
+ check(mdb_cursor_get(cursor->cur, &key, &value, MDB_FIRST));
1807
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1347
1808
  }
1348
1809
 
1349
1810
  /**
@@ -1354,11 +1815,11 @@ static VALUE cursor_first(VALUE self) {
1354
1815
  * nil if no record.
1355
1816
  */
1356
1817
  static VALUE cursor_last(VALUE self) {
1357
- CURSOR(self, cursor);
1358
- MDB_val key, value;
1818
+ CURSOR(self, &lmdb_cursor_type, cursor);
1819
+ MDB_val key, value;
1359
1820
 
1360
- check(mdb_cursor_get(cursor->cur, &key, &value, MDB_LAST));
1361
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1821
+ check(mdb_cursor_get(cursor->cur, &key, &value, MDB_LAST));
1822
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1362
1823
  }
1363
1824
 
1364
1825
  /**
@@ -1369,14 +1830,14 @@ static VALUE cursor_last(VALUE self) {
1369
1830
  * nil if no previous record.
1370
1831
  */
1371
1832
  static VALUE cursor_prev(VALUE self) {
1372
- CURSOR(self, cursor);
1373
- MDB_val key, value;
1833
+ CURSOR(self, &lmdb_cursor_type, cursor);
1834
+ MDB_val key, value;
1374
1835
 
1375
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_PREV);
1376
- if (ret == MDB_NOTFOUND)
1377
- return Qnil;
1378
- check(ret);
1379
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1836
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_PREV);
1837
+ if (ret == MDB_NOTFOUND)
1838
+ return Qnil;
1839
+ check(ret);
1840
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1380
1841
  }
1381
1842
 
1382
1843
  /**
@@ -1388,22 +1849,22 @@ static VALUE cursor_prev(VALUE self) {
1388
1849
  * nil if no next record.
1389
1850
  */
1390
1851
  static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
1391
- CURSOR(self, cursor);
1392
- VALUE nodup;
1393
- MDB_val key, value;
1394
- MDB_cursor_op op = MDB_NEXT;
1852
+ CURSOR(self, &lmdb_cursor_type, cursor);
1853
+ VALUE nodup;
1854
+ MDB_val key, value;
1855
+ MDB_cursor_op op = MDB_NEXT;
1395
1856
 
1396
- rb_scan_args(argc, argv, "01", &nodup);
1857
+ rb_scan_args(argc, argv, "01", &nodup);
1397
1858
 
1398
- if (RTEST(nodup))
1399
- op = MDB_NEXT_NODUP;
1859
+ if (RTEST(nodup))
1860
+ op = MDB_NEXT_NODUP;
1400
1861
 
1401
- int ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1402
- if (ret == MDB_NOTFOUND)
1403
- return Qnil;
1404
- check(ret);
1405
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1406
- rb_str_new(value.mv_data, value.mv_size));
1862
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1863
+ if (ret == MDB_NOTFOUND)
1864
+ return Qnil;
1865
+ check(ret);
1866
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1867
+ rb_str_new(value.mv_data, value.mv_size));
1407
1868
  }
1408
1869
 
1409
1870
  /**
@@ -1416,26 +1877,26 @@ static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
1416
1877
  * nil if no next record or the next record is out of the range.
1417
1878
  */
1418
1879
  static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1419
- CURSOR(self, cursor);
1420
- MDB_val key, value, ub_key;
1880
+ CURSOR(self, &lmdb_cursor_type, cursor);
1881
+ MDB_val key, value, ub_key;
1421
1882
 
1422
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_NEXT);
1423
- if (ret == MDB_NOTFOUND)
1424
- return Qnil;
1425
- check(ret);
1883
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_NEXT);
1884
+ if (ret == MDB_NOTFOUND)
1885
+ return Qnil;
1886
+ check(ret);
1426
1887
 
1427
- ub_key.mv_size = RSTRING_LEN(upper_bound_key);
1428
- ub_key.mv_data = StringValuePtr(upper_bound_key);
1888
+ ub_key.mv_size = RSTRING_LEN(upper_bound_key);
1889
+ ub_key.mv_data = StringValuePtr(upper_bound_key);
1429
1890
 
1430
- MDB_txn* txn = mdb_cursor_txn(cursor->cur);
1431
- MDB_dbi dbi = mdb_cursor_dbi(cursor->cur);
1891
+ MDB_txn* txn = mdb_cursor_txn(cursor->cur);
1892
+ MDB_dbi dbi = mdb_cursor_dbi(cursor->cur);
1432
1893
 
1433
- if (mdb_cmp(txn, dbi, &key, &ub_key) <= 0) {
1434
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1435
- rb_str_new(value.mv_data, value.mv_size));
1436
- } else {
1437
- return Qnil;
1438
- }
1894
+ if (mdb_cmp(txn, dbi, &key, &ub_key) <= 0) {
1895
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1896
+ rb_str_new(value.mv_data, value.mv_size));
1897
+ } else {
1898
+ return Qnil;
1899
+ }
1439
1900
  }
1440
1901
 
1441
1902
  /**
@@ -1446,46 +1907,46 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1446
1907
  * @param value [nil, #to_s] The optional value (+:dupsort+ only)
1447
1908
  * @return [Array] The +[key, value]+ pair to which the cursor now points.
1448
1909
  */
1449
- static VALUE cursor_set(int argc, VALUE* argv, VALUE self) {
1450
- CURSOR(self, cursor);
1451
- VALUE vkey, vval;
1452
- MDB_val key, value;
1453
- MDB_cursor_op op = MDB_SET_KEY;
1454
- int ret;
1910
+ static VALUE cursor_set(int argc, VALUE* argv, VALUE self) {
1911
+ CURSOR(self, &lmdb_cursor_type, cursor);
1912
+ VALUE vkey, vval;
1913
+ MDB_val key, value;
1914
+ MDB_cursor_op op = MDB_SET_KEY;
1915
+ int ret;
1455
1916
 
1456
- rb_scan_args(argc, argv, "11", &vkey, &vval);
1917
+ rb_scan_args(argc, argv, "11", &vkey, &vval);
1457
1918
 
1458
- /*
1459
- XXX TODO: this was a nasty segfault: the key (and any
1460
- non-nil value) should be asserted to be strings, but then
1461
- if the database is `integerkeys` then perhaps we should
1462
- coerce?
1463
- */
1919
+ /*
1920
+ XXX TODO: this was a nasty segfault: the key (and any
1921
+ non-nil value) should be asserted to be strings, but then
1922
+ if the database is `integerkeys` then perhaps we should
1923
+ coerce?
1924
+ */
1464
1925
 
1465
- if (TYPE(vkey) != T_STRING)
1466
- rb_raise(rb_eArgError, "key must be a string");
1926
+ if (TYPE(vkey) != T_STRING)
1927
+ rb_raise(rb_eArgError, "key must be a string");
1467
1928
 
1468
- key.mv_size = RSTRING_LEN(vkey);
1469
- key.mv_data = StringValuePtr(vkey);
1929
+ key.mv_size = RSTRING_LEN(vkey);
1930
+ key.mv_data = StringValuePtr(vkey);
1470
1931
 
1471
- if (!NIL_P(vval)) {
1472
- if (TYPE(vval) != T_STRING)
1473
- rb_raise(rb_eArgError, "non-nil value must be a string");
1932
+ if (!NIL_P(vval)) {
1933
+ if (TYPE(vval) != T_STRING)
1934
+ rb_raise(rb_eArgError, "non-nil value must be a string");
1474
1935
 
1475
- op = MDB_GET_BOTH;
1476
- value.mv_size = RSTRING_LEN(vval);
1477
- value.mv_data = StringValuePtr(vval);
1478
- }
1936
+ op = MDB_GET_BOTH;
1937
+ value.mv_size = RSTRING_LEN(vval);
1938
+ value.mv_data = StringValuePtr(vval);
1939
+ }
1479
1940
 
1480
- ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1941
+ ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1481
1942
 
1482
- if (!NIL_P(vval) && ret == MDB_NOTFOUND)
1483
- return Qnil;
1943
+ if (!NIL_P(vval) && ret == MDB_NOTFOUND)
1944
+ return Qnil;
1484
1945
 
1485
- check(ret);
1946
+ check(ret);
1486
1947
 
1487
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1488
- rb_str_new(value.mv_data, value.mv_size));
1948
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1949
+ rb_str_new(value.mv_data, value.mv_size));
1489
1950
  }
1490
1951
 
1491
1952
  /**
@@ -1495,22 +1956,22 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1495
1956
  * @return [Array] The [key, value] pair to which the cursor now points.
1496
1957
  */
1497
1958
  static VALUE cursor_set_range(VALUE self, VALUE vkey) {
1498
- CURSOR(self, cursor);
1499
- MDB_val key, value;
1500
- int ret;
1959
+ CURSOR(self, &lmdb_cursor_type, cursor);
1960
+ MDB_val key, value;
1961
+ int ret;
1501
1962
 
1502
- key.mv_size = RSTRING_LEN(vkey);
1503
- key.mv_data = StringValuePtr(vkey);
1963
+ key.mv_size = RSTRING_LEN(vkey);
1964
+ key.mv_data = StringValuePtr(vkey);
1504
1965
 
1505
- ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
1966
+ ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
1506
1967
 
1507
- /* not sure why we were letting this throw an exception */
1508
- if (ret == MDB_NOTFOUND) return Qnil;
1968
+ /* not sure why we were letting this throw an exception */
1969
+ if (ret == MDB_NOTFOUND) return Qnil;
1509
1970
 
1510
- check(ret);
1971
+ check(ret);
1511
1972
 
1512
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1513
- rb_str_new(value.mv_data, value.mv_size));
1973
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1974
+ rb_str_new(value.mv_data, value.mv_size));
1514
1975
  }
1515
1976
 
1516
1977
  /**
@@ -1520,14 +1981,14 @@ static VALUE cursor_set_range(VALUE self, VALUE vkey) {
1520
1981
  */
1521
1982
 
1522
1983
  static VALUE cursor_get(VALUE self) {
1523
- CURSOR(self, cursor);
1984
+ CURSOR(self, &lmdb_cursor_type, cursor);
1524
1985
 
1525
- MDB_val key, value;
1526
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_GET_CURRENT);
1527
- if (ret == MDB_NOTFOUND)
1528
- return Qnil;
1529
- check(ret);
1530
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1986
+ MDB_val key, value;
1987
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_GET_CURRENT);
1988
+ if (ret == MDB_NOTFOUND)
1989
+ return Qnil;
1990
+ check(ret);
1991
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1531
1992
  }
1532
1993
 
1533
1994
  #define METHOD cursor_put_flags
@@ -1568,32 +2029,32 @@ static VALUE cursor_get(VALUE self) {
1568
2029
  * data.
1569
2030
  */
1570
2031
  static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
1571
- CURSOR(self, cursor);
2032
+ CURSOR(self, &lmdb_cursor_type, cursor);
1572
2033
 
1573
- VALUE vkey, vval, option_hash;
2034
+ VALUE vkey, vval, option_hash;
1574
2035
  #ifdef RB_SCAN_ARGS_KEYWORDS
1575
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1576
- argc, argv, "2:", &vkey, &vval, &option_hash);
2036
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
2037
+ argc, argv, "2:", &vkey, &vval, &option_hash);
1577
2038
  #else
1578
- rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
2039
+ rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
1579
2040
  #endif
1580
2041
 
1581
- int flags = 0;
1582
- if (!NIL_P(option_hash))
1583
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))cursor_put_flags,
1584
- (VALUE)&flags);
2042
+ int flags = 0;
2043
+ if (!NIL_P(option_hash))
2044
+ rb_hash_foreach(option_hash, (int (*)(ANYARGS))cursor_put_flags,
2045
+ (VALUE)&flags);
1585
2046
 
1586
- vkey = StringValue(vkey);
1587
- vval = StringValue(vval);
2047
+ vkey = StringValue(vkey);
2048
+ vval = StringValue(vval);
1588
2049
 
1589
- MDB_val key, value;
1590
- key.mv_size = RSTRING_LEN(vkey);
1591
- key.mv_data = RSTRING_PTR(vkey);
1592
- value.mv_size = RSTRING_LEN(vval);
1593
- value.mv_data = RSTRING_PTR(vval);
2050
+ MDB_val key, value;
2051
+ key.mv_size = RSTRING_LEN(vkey);
2052
+ key.mv_data = RSTRING_PTR(vkey);
2053
+ value.mv_size = RSTRING_LEN(vval);
2054
+ value.mv_data = RSTRING_PTR(vval);
1594
2055
 
1595
- check(mdb_cursor_put(cursor->cur, &key, &value, flags));
1596
- return Qnil;
2056
+ check(mdb_cursor_put(cursor->cur, &key, &value, flags));
2057
+ return Qnil;
1597
2058
  }
1598
2059
 
1599
2060
  #define METHOD cursor_delete_flags
@@ -1606,28 +2067,28 @@ static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
1606
2067
  * @overload delete(options)
1607
2068
  * Delete current key/data pair.
1608
2069
  * This function deletes the key/data pair to which the cursor refers.
1609
- * @option options [Boolean] :nodupdata Delete all of the data
2070
+ * @option options [Boolean] :nodupdata Delete all of the data
1610
2071
  * items for the current key. This flag may only be specified
1611
2072
  * if the database was opened with +:dupsort+.
1612
2073
  */
1613
2074
  static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
1614
- CURSOR(self, cursor);
2075
+ CURSOR(self, &lmdb_cursor_type, cursor);
1615
2076
 
1616
- VALUE option_hash;
2077
+ VALUE option_hash;
1617
2078
  #ifdef RB_SCAN_ARGS_KEYWORDS
1618
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1619
- argc, argv, ":", &option_hash);
2079
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
2080
+ argc, argv, ":", &option_hash);
1620
2081
  #else
1621
- rb_scan_args(argc, argv, ":", &option_hash);
2082
+ rb_scan_args(argc, argv, ":", &option_hash);
1622
2083
  #endif
1623
2084
 
1624
- int flags = 0;
1625
- if (!NIL_P(option_hash))
1626
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))cursor_delete_flags,
1627
- (VALUE)&flags);
2085
+ int flags = 0;
2086
+ if (!NIL_P(option_hash))
2087
+ rb_hash_foreach(option_hash, (int (*)(ANYARGS))cursor_delete_flags,
2088
+ (VALUE)&flags);
1628
2089
 
1629
- check(mdb_cursor_del(cursor->cur, flags));
1630
- return Qnil;
2090
+ check(mdb_cursor_del(cursor->cur, flags));
2091
+ return Qnil;
1631
2092
  }
1632
2093
 
1633
2094
  /**
@@ -1635,8 +2096,8 @@ static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
1635
2096
  * @return [Database] the database which this cursor is iterating over.
1636
2097
  */
1637
2098
  static VALUE cursor_db(VALUE self) {
1638
- CURSOR(self, cursor);
1639
- return cursor->db;
2099
+ CURSOR(self, &lmdb_cursor_type, cursor);
2100
+ return cursor->db;
1640
2101
  }
1641
2102
 
1642
2103
  /**
@@ -1647,231 +2108,251 @@ static VALUE cursor_db(VALUE self) {
1647
2108
  * @return [Number] count of duplicates
1648
2109
  */
1649
2110
  static VALUE cursor_count(VALUE self) {
1650
- CURSOR(self, cursor);
1651
- size_t count;
1652
- check(mdb_cursor_count(cursor->cur, &count));
1653
- return SIZET2NUM(count);
2111
+ CURSOR(self, &lmdb_cursor_type, cursor);
2112
+ size_t count;
2113
+ check(mdb_cursor_count(cursor->cur, &count));
2114
+ return SIZET2NUM(count);
1654
2115
  }
1655
2116
 
1656
2117
  void Init_lmdb_ext() {
1657
- VALUE mLMDB;
1658
-
1659
- /**
1660
- * Document-module: LMDB
1661
- *
1662
- * The LMDB module presents a Ruby API to the OpenLDAP Lightning Memory-mapped Database (LMDB).
1663
- * @see http://symas.com/mdb/
1664
- */
1665
- mLMDB = rb_define_module("LMDB");
1666
- rb_define_const(mLMDB, "LIB_VERSION", rb_str_new2(MDB_VERSION_STRING));
1667
- rb_define_singleton_method(mLMDB, "new", environment_new, -1);
2118
+ VALUE mLMDB;
2119
+
2120
+ /**
2121
+ * Document-module: LMDB
2122
+ *
2123
+ * The LMDB module presents a Ruby API to the OpenLDAP Lightning Memory-mapped Database (LMDB).
2124
+ * @see http://symas.com/mdb/
2125
+ */
2126
+ mLMDB = rb_define_module("LMDB");
2127
+ rb_define_const(mLMDB, "LIB_VERSION", rb_str_new2(MDB_VERSION_STRING));
2128
+ rb_define_singleton_method(mLMDB, "new", environment_new, -1);
1668
2129
 
1669
2130
  #define VERSION_CONST(name) rb_define_const(mLMDB, "LIB_VERSION_"#name, INT2NUM(MDB_VERSION_##name));
1670
- VERSION_CONST(MAJOR)
1671
- VERSION_CONST(MINOR)
1672
- VERSION_CONST(PATCH)
2131
+ VERSION_CONST(MAJOR)
2132
+ VERSION_CONST(MINOR)
2133
+ VERSION_CONST(PATCH)
1673
2134
  #undef VERSION_CONST
1674
2135
 
1675
- /**
1676
- * Document-class: LMDB::Error
1677
- *
1678
- * A general class of exceptions raised within the LMDB gem.
1679
- */
1680
- cError = rb_define_class_under(mLMDB, "Error", rb_eRuntimeError);
1681
- #define ERROR(name) \
1682
- cError_##name = rb_define_class_under(cError, #name, cError);
2136
+ /**
2137
+ * Document-class: LMDB::Error
2138
+ *
2139
+ * A general class of exceptions raised within the LMDB gem.
2140
+ */
2141
+ cError = rb_define_class_under(mLMDB, "Error", rb_eRuntimeError);
2142
+ #define ERROR(name) \
2143
+ cError_##name = rb_define_class_under(cError, #name, cError);
1683
2144
  #include "errors.h"
1684
2145
  #undef ERROR
1685
2146
 
1686
- /**
1687
- * Document-class: LMDB::Environment
1688
- *
1689
- * The Environment is the root object for all LMDB operations.
1690
- *
1691
- * An LMDB "environment" is a collection of one or more "databases"
1692
- * (key-value tables), along with transactions to modify those
1693
- * databases and cursors to iterate through them.
1694
- *
1695
- * An environment -- and its collection of databases -- is normally
1696
- * stored in a directory. That directory will contain two files:
1697
- * * +data.mdb+: all the records in all the databases in the environment
1698
- * * +lock.mdb+: state of transactions that may be going on in the environment.
1699
- *
1700
- * An environment can contain multiple databases. Each of the
1701
- * databases has a string name ("mydatabase", "db.3.1982"). You use
1702
- * the database name to open the database within the environment.
1703
- *
1704
- * @example The normal pattern for using LMDB in Ruby
1705
- * env = LMDB.new "databasedir"
1706
- * db = env.database "databasename"
1707
- * # ... do things to the database ...
1708
- * env.close
1709
- */
1710
- cEnvironment = rb_define_class_under(mLMDB, "Environment", rb_cObject);
1711
- rb_undef_alloc_func(cEnvironment);
1712
- rb_define_singleton_method(cEnvironment, "new", environment_new, -1);
1713
- rb_define_method(cEnvironment, "database", environment_database, -1);
1714
- rb_define_method(cEnvironment, "databases", environment_databases, 0);
1715
- rb_define_method(cEnvironment, "active_txn", environment_active_txn, 0);
1716
- rb_define_method(cEnvironment, "close", environment_close, 0);
1717
- rb_define_method(cEnvironment, "stat", environment_stat, 0);
1718
- rb_define_method(cEnvironment, "info", environment_info, 0);
1719
- rb_define_method(cEnvironment, "copy", environment_copy, 1);
1720
- rb_define_method(cEnvironment, "sync", environment_sync, -1);
1721
- rb_define_method(cEnvironment, "mapsize=", environment_set_mapsize, 1);
1722
- rb_define_method(cEnvironment, "set_flags", environment_set_flags, -1);
1723
- rb_define_method(cEnvironment, "clear_flags", environment_clear_flags, -1);
1724
- rb_define_method(cEnvironment, "flags", environment_flags, 0);
1725
- rb_define_method(cEnvironment, "path", environment_path, 0);
1726
- rb_define_method(cEnvironment, "transaction", environment_transaction, -1);
1727
-
1728
- /**
1729
- * Document-class: LMDB::Database
1730
- *
1731
- * An LMDB Database is a table of key-value pairs. It is stored as
1732
- * part of the {Environment}.
1733
- *
1734
- * By default, each key in a Database maps to one value. However, a
1735
- * Database can be configured at creation to allow duplicate keys, in
1736
- * which case one key will map to multiple values.
1737
- *
1738
- * A Database stores the keys in a sorted order. The order can also
1739
- * be set with options when the database is created.
1740
- *
1741
- * The basic operations on a database are to {#put}, {#get}, and
1742
- * {#delete} records. One can also iterate through the records in a
1743
- * database using a {Cursor}.
1744
- *
1745
- * @example Typical usage
1746
- * env = LMDB.new "databasedir"
1747
- * db = env.database "databasename"
1748
- * db.put "key1", "value1"
1749
- * db.put "key2", "value2"
1750
- * db.get "key1" #=> "value1"
1751
- * env.close
1752
- */
1753
- cDatabase = rb_define_class_under(mLMDB, "Database", rb_cObject);
1754
- rb_undef_alloc_func(cDatabase);
1755
- rb_undef_method(rb_singleton_class(cDatabase), "new");
1756
- rb_define_method(cDatabase, "stat", database_stat, 0);
1757
- rb_define_method(cDatabase, "flags", database_get_flags, 0);
1758
- rb_define_method(cDatabase, "dupsort?", database_is_dupsort, 0);
1759
- rb_define_method(cDatabase, "dupfixed?", database_is_dupfixed, 0);
1760
- rb_define_method(cDatabase, "drop", database_drop, 0);
1761
- rb_define_method(cDatabase, "clear", database_clear, 0);
1762
- rb_define_method(cDatabase, "get", database_get, 1);
1763
- rb_define_method(cDatabase, "put", database_put, -1);
1764
- rb_define_method(cDatabase, "delete", database_delete, -1);
1765
- rb_define_method(cDatabase, "cursor", database_cursor, 0);
1766
- rb_define_method(cDatabase, "env", database_env, 0);
1767
-
1768
- /**
1769
- * Document-class: LMDB::Transaction
1770
- *
1771
- * The LMDB environment supports transactional reads and updates. By
1772
- * default, these provide the standard ACID (atomicity, consistency,
1773
- * isolation, durability) behaviors.
1774
- *
1775
- * Transactions can be committed or aborted. When a transaction is
1776
- * committed, all its effects take effect in the database atomically.
1777
- * When a transaction is aborted, none of its effects take effect.
1778
- *
1779
- * Transactions span the entire environment. All the updates made in
1780
- * the course of an update transaction -- writing records across all
1781
- * databases, creating databases, and destroying databases -- are
1782
- * either completed atomically or rolled back.
1783
- *
1784
- * Transactions can be nested. A child transaction can be started
1785
- * within a parent transaction. The child transaction can commit or
1786
- * abort, at which point the effects of the child become visible to
1787
- * the parent transaction or not. If the parent aborts, all of the
1788
- * changes performed in the context of the parent -- including the
1789
- * changes from a committed child transaction -- are rolled back.
1790
- *
1791
- * To create a transaction, call {Environment#transaction} and supply
1792
- * a block for the code to execute in that transaction.
1793
- *
1794
- * @example Typical usage
1795
- * env = LMDB.new "databasedir"
1796
- * db1 = env.database "database1"
1797
- * env.transaction do |parent|
1798
- * db2 = env.database "database2", :create => true
1799
- * #=> creates a new database, but it isn't
1800
- * #=> yet committed to storage
1801
- * db1['x'] #=> nil
1802
- * env.transaction do |child1|
1803
- * db2['a'] = 'b'
1804
- * db1['x'] = 'y'
1805
- * end
1806
- * #=> first child transaction commits
1807
- * #=> changes are visible within the parent transaction
1808
- * #=> but are not yet permanent
1809
- * db1['x'] #=> 'y'
1810
- * db2['a'] #=> 'a'
1811
- * env.transaction do |child2|
1812
- * db2['a'] = 'def'
1813
- * db1['x'] = 'ghi'
1814
- * child2.abort
1815
- * #=> second child transaction aborts and rolls
1816
- * #=> back its changes
1817
- * end
1818
- * db1['x'] #=> 'y'
1819
- * db2['a'] #=> 'a'
1820
- * end
1821
- * #=> parent transaction commits and writes database2
1822
- * #=> and the updates from transaction child1 to
1823
- * #=> storage.
1824
- */
1825
- cTransaction = rb_define_class_under(mLMDB, "Transaction", rb_cObject);
1826
- rb_undef_alloc_func(cTransaction);
1827
- rb_undef_method(rb_singleton_class(cTransaction), "new");
1828
- rb_define_method(cTransaction, "commit", transaction_commit, 0);
1829
- rb_define_method(cTransaction, "abort", transaction_abort, 0);
1830
- rb_define_method(cTransaction, "env", transaction_env, 0);
1831
- rb_define_method(cTransaction, "readonly?", transaction_is_readonly, 0);
1832
- rb_define_method(cTransaction, "finished?", transaction_is_finished, 0);
1833
- rb_define_method(cTransaction, "error?", transaction_is_error, 0);
1834
-
1835
- /**
1836
- * Document-class: LMDB::Cursor
1837
- *
1838
- * A Cursor points to records in a database, and is used to iterate
1839
- * through the records in the database.
1840
- *
1841
- * Cursors are created in the context of a transaction, and should
1842
- * only be used as long as that transaction is active. In other words,
1843
- * after you {Transaction#commit} or {Transaction#abort} a transaction,
1844
- * the cursors created while that transaction was active are no longer
1845
- * usable.
1846
- *
1847
- * To create a cursor, call {Database#cursor} and pass it a block for
1848
- * that should be performed using the cursor.
1849
- *
1850
- * @example Typical usage
1851
- * env = LMDB.new "databasedir"
1852
- * db = env.database "databasename"
1853
- * db.cursor do |cursor|
1854
- * rl = cursor.last #=> content of the last record
1855
- * r1 = cursor.first #=> content of the first record
1856
- * r2 = cursor.next #=> content of the second record
1857
- * cursor.put "x", "y", current: true
1858
- * #=> replaces the second record with a new value "y"
1859
- * end
1860
- */
1861
- cCursor = rb_define_class_under(mLMDB, "Cursor", rb_cObject);
1862
- rb_undef_alloc_func(cCursor);
1863
- rb_undef_method(rb_singleton_class(cCursor), "new");
1864
- rb_define_method(cCursor, "close", cursor_close, 0);
1865
- rb_define_method(cCursor, "get", cursor_get, 0);
1866
- rb_define_method(cCursor, "first", cursor_first, 0);
1867
- rb_define_method(cCursor, "last", cursor_last, 0);
1868
- rb_define_method(cCursor, "next", cursor_next, -1);
1869
- rb_define_method(cCursor, "next_range", cursor_next_range, 1);
1870
- rb_define_method(cCursor, "prev", cursor_prev, 0);
1871
- rb_define_method(cCursor, "set", cursor_set, -1);
1872
- rb_define_method(cCursor, "set_range", cursor_set_range, 1);
1873
- rb_define_method(cCursor, "put", cursor_put, -1);
1874
- rb_define_method(cCursor, "count", cursor_count, 0);
1875
- rb_define_method(cCursor, "delete", cursor_delete, -1);
1876
- rb_define_method(cCursor, "database", cursor_db, 0);
2147
+ /**
2148
+ * Document-class: LMDB::Environment
2149
+ *
2150
+ * The Environment is the root object for all LMDB operations.
2151
+ *
2152
+ * An LMDB "environment" is a collection of one or more "databases"
2153
+ * (key-value tables), along with transactions to modify those
2154
+ * databases and cursors to iterate through them.
2155
+ *
2156
+ * An environment -- and its collection of databases -- is normally
2157
+ * stored in a directory. That directory will contain two files:
2158
+ * * +data.mdb+: all the records in all the databases in the environment
2159
+ * * +lock.mdb+: state of transactions that may be going on in the environment.
2160
+ *
2161
+ * An environment can contain multiple databases. Each of the
2162
+ * databases has a string name ("mydatabase", "db.3.1982"). You use
2163
+ * the database name to open the database within the environment.
2164
+ *
2165
+ * @example The normal pattern for using LMDB in Ruby
2166
+ * env = LMDB.new "databasedir"
2167
+ * db = env.database "databasename"
2168
+ * # ... do things to the database ...
2169
+ * env.close
2170
+ */
2171
+ cEnvironment = rb_define_class_under(mLMDB, "Environment", rb_cObject);
2172
+ rb_undef_alloc_func(cEnvironment);
2173
+ rb_define_singleton_method(cEnvironment, "new", environment_new, -1);
2174
+ rb_define_method(cEnvironment, "databases", environment_databases, 0);
2175
+ rb_define_method(cEnvironment, "database", environment_database, -1);
2176
+ rb_define_method(cEnvironment, "[]", environment_database_aref, 1);
2177
+ rb_define_method(cEnvironment, "active_txn", environment_active_txn, 0);
2178
+ rb_define_method(cEnvironment, "close", environment_close, 0);
2179
+ rb_define_method(cEnvironment, "stat", environment_stat, 0);
2180
+ rb_define_method(cEnvironment, "info", environment_info, 0);
2181
+ rb_define_method(cEnvironment, "copy", environment_copy, 1);
2182
+ rb_define_method(cEnvironment, "sync", environment_sync, -1);
2183
+ rb_define_method(cEnvironment, "mapsize=", environment_set_mapsize, 1);
2184
+ rb_define_method(cEnvironment, "set_flags", environment_set_flags, -1);
2185
+ rb_define_method(cEnvironment, "clear_flags", environment_clear_flags, -1);
2186
+ rb_define_method(cEnvironment, "flags", environment_flags, 0);
2187
+ rb_define_method(cEnvironment, "path", environment_path, 0);
2188
+ rb_define_method(cEnvironment, "transaction", environment_transaction, -1);
2189
+ rb_define_method(cEnvironment, "reader_list", environment_reader_list, 0);
2190
+ rb_define_method(cEnvironment, "reader_check", environment_reader_check, 0);
2191
+ /*
2192
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
2193
+ rb_define_method(cEnvironment, "rb_gc_compact", environment_compact_m, 0);
2194
+ #endif
2195
+ */
2196
+
2197
+ /**
2198
+ * Document-class: LMDB::Database
2199
+ *
2200
+ * An LMDB Database is a table of key-value pairs. It is stored as
2201
+ * part of the {Environment}.
2202
+ *
2203
+ * By default, each key in a Database maps to one value. However, a
2204
+ * Database can be configured at creation to allow duplicate keys, in
2205
+ * which case one key will map to multiple values.
2206
+ *
2207
+ * A Database stores the keys in a sorted order. The order can also
2208
+ * be set with options when the database is created.
2209
+ *
2210
+ * The basic operations on a database are to {#put}, {#get}, and
2211
+ * {#delete} records. One can also iterate through the records in a
2212
+ * database using a {Cursor}.
2213
+ *
2214
+ * @example Typical usage
2215
+ * env = LMDB.new "databasedir"
2216
+ * db = env.database "databasename"
2217
+ * db.put "key1", "value1"
2218
+ * db.put "key2", "value2"
2219
+ * db.get "key1" #=> "value1"
2220
+ * env.close
2221
+ */
2222
+ cDatabase = rb_define_class_under(mLMDB, "Database", rb_cObject);
2223
+ rb_undef_alloc_func(cDatabase);
2224
+ rb_undef_method(rb_singleton_class(cDatabase), "new");
2225
+ rb_define_method(cDatabase, "stat", database_stat, 0);
2226
+ rb_define_method(cDatabase, "flags", database_get_flags, 0);
2227
+ rb_define_method(cDatabase, "dupsort?", database_is_dupsort, 0);
2228
+ rb_define_method(cDatabase, "dupfixed?", database_is_dupfixed, 0);
2229
+ rb_define_method(cDatabase, "drop", database_drop, 0);
2230
+ rb_define_method(cDatabase, "clear", database_clear, 0);
2231
+ rb_define_method(cDatabase, "get", database_get, 1);
2232
+ rb_define_method(cDatabase, "put", database_put, -1);
2233
+ rb_define_method(cDatabase, "delete", database_delete, -1);
2234
+ rb_define_method(cDatabase, "cursor", database_cursor, 0);
2235
+ rb_define_method(cDatabase, "env", database_env, 0);
2236
+ /*
2237
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
2238
+ rb_define_method(cDatabase, "rb_gc_compact", database_compact_m, 0);
2239
+ #endif
2240
+ */
2241
+ /**
2242
+ * Document-class: LMDB::Transaction
2243
+ *
2244
+ * The LMDB environment supports transactional reads and updates. By
2245
+ * default, these provide the standard ACID (atomicity, consistency,
2246
+ * isolation, durability) behaviors.
2247
+ *
2248
+ * Transactions can be committed or aborted. When a transaction is
2249
+ * committed, all its effects take effect in the database atomically.
2250
+ * When a transaction is aborted, none of its effects take effect.
2251
+ *
2252
+ * Transactions span the entire environment. All the updates made in
2253
+ * the course of an update transaction -- writing records across all
2254
+ * databases, creating databases, and destroying databases -- are
2255
+ * either completed atomically or rolled back.
2256
+ *
2257
+ * Transactions can be nested. A child transaction can be started
2258
+ * within a parent transaction. The child transaction can commit or
2259
+ * abort, at which point the effects of the child become visible to
2260
+ * the parent transaction or not. If the parent aborts, all of the
2261
+ * changes performed in the context of the parent -- including the
2262
+ * changes from a committed child transaction -- are rolled back.
2263
+ *
2264
+ * To create a transaction, call {Environment#transaction} and supply
2265
+ * a block for the code to execute in that transaction.
2266
+ *
2267
+ * @example Typical usage
2268
+ * env = LMDB.new "databasedir"
2269
+ * db1 = env.database "database1"
2270
+ * env.transaction do |parent|
2271
+ * db2 = env.database "database2", :create => true
2272
+ * #=> creates a new database, but it isn't
2273
+ * #=> yet committed to storage
2274
+ * db1['x'] #=> nil
2275
+ * env.transaction do |child1|
2276
+ * db2['a'] = 'b'
2277
+ * db1['x'] = 'y'
2278
+ * end
2279
+ * #=> first child transaction commits
2280
+ * #=> changes are visible within the parent transaction
2281
+ * #=> but are not yet permanent
2282
+ * db1['x'] #=> 'y'
2283
+ * db2['a'] #=> 'a'
2284
+ * env.transaction do |child2|
2285
+ * db2['a'] = 'def'
2286
+ * db1['x'] = 'ghi'
2287
+ * child2.abort
2288
+ * #=> second child transaction aborts and rolls
2289
+ * #=> back its changes
2290
+ * end
2291
+ * db1['x'] #=> 'y'
2292
+ * db2['a'] #=> 'a'
2293
+ * end
2294
+ * #=> parent transaction commits and writes database2
2295
+ * #=> and the updates from transaction child1 to
2296
+ * #=> storage.
2297
+ */
2298
+ cTransaction = rb_define_class_under(mLMDB, "Transaction", rb_cObject);
2299
+ rb_undef_alloc_func(cTransaction);
2300
+ rb_undef_method(rb_singleton_class(cTransaction), "new");
2301
+ rb_define_method(cTransaction, "commit", transaction_commit, 0);
2302
+ rb_define_method(cTransaction, "abort", transaction_abort, 0);
2303
+ rb_define_method(cTransaction, "env", transaction_env, 0);
2304
+ rb_define_method(cTransaction, "readonly?", transaction_is_readonly, 0);
2305
+ rb_define_method(cTransaction, "finished?", transaction_is_finished, 0);
2306
+ rb_define_method(cTransaction, "error?", transaction_is_error, 0);
2307
+ /*
2308
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
2309
+ rb_define_method(cTransaction, "rb_gc_compact", transaction_compact_m, 0);
2310
+ #endif*/
2311
+ /**
2312
+ * Document-class: LMDB::Cursor
2313
+ *
2314
+ * A Cursor points to records in a database, and is used to iterate
2315
+ * through the records in the database.
2316
+ *
2317
+ * Cursors are created in the context of a transaction, and should
2318
+ * only be used as long as that transaction is active. In other words,
2319
+ * after you {Transaction#commit} or {Transaction#abort} a transaction,
2320
+ * the cursors created while that transaction was active are no longer
2321
+ * usable.
2322
+ *
2323
+ * To create a cursor, call {Database#cursor} and pass it a block for
2324
+ * that should be performed using the cursor.
2325
+ *
2326
+ * @example Typical usage
2327
+ * env = LMDB.new "databasedir"
2328
+ * db = env.database "databasename"
2329
+ * db.cursor do |cursor|
2330
+ * rl = cursor.last #=> content of the last record
2331
+ * r1 = cursor.first #=> content of the first record
2332
+ * r2 = cursor.next #=> content of the second record
2333
+ * cursor.put "x", "y", current: true
2334
+ * #=> replaces the second record with a new value "y"
2335
+ * end
2336
+ */
2337
+ cCursor = rb_define_class_under(mLMDB, "Cursor", rb_cObject);
2338
+ rb_undef_alloc_func(cCursor);
2339
+ rb_undef_method(rb_singleton_class(cCursor), "new");
2340
+ rb_define_method(cCursor, "close", cursor_close, 0);
2341
+ rb_define_method(cCursor, "get", cursor_get, 0);
2342
+ rb_define_method(cCursor, "first", cursor_first, 0);
2343
+ rb_define_method(cCursor, "last", cursor_last, 0);
2344
+ rb_define_method(cCursor, "next", cursor_next, -1);
2345
+ rb_define_method(cCursor, "next_range", cursor_next_range, 1);
2346
+ rb_define_method(cCursor, "prev", cursor_prev, 0);
2347
+ rb_define_method(cCursor, "set", cursor_set, -1);
2348
+ rb_define_method(cCursor, "set_range", cursor_set_range, 1);
2349
+ rb_define_method(cCursor, "put", cursor_put, -1);
2350
+ rb_define_method(cCursor, "count", cursor_count, 0);
2351
+ rb_define_method(cCursor, "delete", cursor_delete, -1);
2352
+ rb_define_method(cCursor, "database", cursor_db, 0);
2353
+ /*
2354
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
2355
+ rb_define_method(cCursor, "rb_gc_compact", cursor_compact_m, 0);
2356
+ #endif
2357
+ */
1877
2358
  }