lmdb 0.7.5 → 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.
@@ -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,118 +212,259 @@ 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
+ }
225
309
 
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);
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);
313
+
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);
356
+ }
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;
248
387
  }
388
+ */
389
+
390
+ // and now your regularly scheduled program
249
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
 
271
470
  // adding the break so we can commit on break
@@ -273,6 +472,7 @@ static void stop_txn_begin(void *arg)
273
472
  #define TAG_BREAK 0x2
274
473
  #endif
275
474
 
475
+
276
476
  /**
277
477
  * This is the code that opens transactions. Read-write transactions
278
478
  * have to be called outside the GVL because they will block otherwise.
@@ -311,157 +511,240 @@ static void stop_txn_begin(void *arg)
311
511
  *
312
512
  */
313
513
 
314
- static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flags) {
315
- 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);
316
517
 
317
- MDB_txn* txn;
318
- TxnArgs txn_args;
518
+ VALUE thread = rb_thread_current();
319
519
 
320
- VALUE thread = rb_thread_current();
321
- VALUE vparent = environment_active_txn(venv);
520
+ // ATTEMPT TO OBTAIN PARENT TRANSACTION (which may be a pseudo)
322
521
 
323
- //
522
+ VALUE vparent = environment_active_txn(venv);
324
523
  Transaction* tparent = NULL;
325
- if (vparent && !NIL_P(vparent))
326
- Data_Get_Struct(vparent, Transaction, tparent);
524
+ if (REXISTS(vparent))
525
+ TypedData_Get_Struct(vparent, Transaction, &lmdb_transaction_type, tparent);
327
526
 
328
- /*
329
- * If the requested transaction is read-only and there is a parent
330
- * transaction (whether read-only or not), we need to use the
331
- * parent transaction.
332
- *
333
- * XXX except conceivably you could do a transaction (RO or RW)
334
- * that spawns a thread that opens another transaction.
335
- *
336
- * note we do *NOT* re-begin the parent transaction, nor do we
337
- * want to commit it
338
- *
339
- */
527
+ // (parent transaction will necessarily be on the same thread by
528
+ // dint of how it is looked up)
340
529
 
341
- if (tparent && flags & MDB_RDONLY) {
342
- // We are reusing the parent transaction.
530
+ // ACQUIRE THE TRANSACTION FROM LMDB
343
531
 
344
- int exception = 0;
345
- VALUE ret = rb_protect(fn, NIL_P(arg) ? vparent : arg, &exception);
532
+ MDB_txn* txn = NULL;
533
+ TxnArgs txn_args;
346
534
 
347
- if (exception) {
348
- // we only abort if there is a bona fide exception, ie not an early break
349
- if (vparent == environment_active_txn(venv) && exception != TAG_BREAK)
350
- transaction_abort(vparent);
351
- rb_jump_tag(exception);
352
- }
353
- 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);
354
571
  }
355
572
  else {
356
- // We are creating a new transaction.
357
-
358
- // XXX note this is a cursed goto loop that could almost certainly
359
- // be rewritten as a do-while
360
- retry:
361
- txn = NULL;
362
-
363
- txn_args.env = environment->env;
364
- txn_args.parent = active_txn(venv); // this returns an MDB_txn
365
- txn_args.flags = flags;
366
- txn_args.htxn = &txn;
367
- txn_args.result = 0;
368
- txn_args.stop = 0;
369
-
370
- if (flags & MDB_RDONLY)
371
- call_txn_begin(&txn_args);
372
- else {
373
- if (tparent) {
374
- // first we have to determine if we're on the same thread
375
- // as the parent, which in turn must be the same as the
376
- // environment's registry for which thread has the
377
- // read-write transaction
378
- if (thread != tparent->thread ||
379
- thread != environment->rw_txn_thread)
380
- rb_raise(cError,
381
- "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);
382
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);
383
599
 
384
- // try to acquire the new transaction
385
- CALL_WITHOUT_GVL(call_txn_begin, &txn_args, stop_txn_begin, &txn_args);
386
-
387
- if (txn_args.stop || !txn) {
388
- // !txn is when rb_thread_call_without_gvl2
389
- // returns before calling txn_begin
390
- if (txn) {
391
- mdb_txn_abort(txn);
392
- txn_args.result = 0;
393
- }
394
-
395
- //rb_warn("got here lol");
396
- rb_thread_check_ints();
397
- goto retry; // in what cases do we get here?
600
+ txn = NULL;
601
+ txn_args.result = 0;
398
602
  }
399
603
 
400
- // set the thread
401
- environment->rw_txn_thread = thread;
402
- }
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);
403
613
 
404
- // this will raise unless result is zero
405
614
  check(txn_args.result);
615
+ }
406
616
 
407
- Transaction* transaction;
408
- VALUE vtxn = Data_Make_Struct(cTransaction, Transaction, transaction_mark,
409
- transaction_free, transaction);
410
- transaction->parent = vparent;
411
- transaction->env = venv;
412
- transaction->txn = txn;
413
- transaction->flags = flags;
414
- transaction->thread = rb_thread_current();
415
- transaction->cursors = rb_ary_new();
617
+ // we should have the transaction handle now, so set up the struct
416
618
 
417
- // set the parent's child to self
418
- if (tparent) tparent->child = vtxn;
619
+ Transaction* transaction;
620
+ VALUE vtxn = TypedData_Make_Struct(cTransaction, Transaction,
621
+ &lmdb_transaction_type, transaction);
419
622
 
420
- 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();
421
628
 
422
- // now we run the function in the transaction
423
- int exception = 0;
424
- 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
+ }
425
638
 
426
- if (exception) {
427
- // rb_warn("lol got exception");
428
- if (vtxn == environment_active_txn(venv)) {
429
- exception == TAG_BREAK ?
430
- transaction_commit(vtxn) : transaction_abort(vtxn);
431
- }
432
- rb_jump_tag(exception);
433
- }
639
+ // set parent's child to self
640
+ if (tparent) tparent->child = vtxn;
434
641
 
435
- // no-exception behaviour is to commit the transaction
436
- if (vtxn == environment_active_txn(venv)) transaction_commit(vtxn);
437
- return ret;
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) {
661
+ if (vtxn == environment_active_txn(venv))
662
+ transaction_finish(vtxn, exception == TAG_BREAK);
663
+
664
+ rb_jump_tag(exception);
438
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;
439
671
  }
440
672
 
441
673
  static void environment_check(Environment* environment) {
442
- if (!environment->env)
443
- rb_raise(cError, "Environment is closed");
444
- }
445
-
446
- static void environment_free(Environment *environment) {
447
- if (environment->env) {
448
- // rb_warn("Memory leak - Garbage collecting open environment");
449
- if (!RHASH_EMPTY_P(environment->txn_thread_hash)) {
450
- // If a transaction (or cursor) is open, its block is on the
451
- // stack, so it will not be collected, so environment_free
452
- // should not be called.
453
- rb_warn("Bug: closing environment with open transactions.");
454
- }
455
- mdb_env_close(environment->env);
456
- }
457
- 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
+ }
458
712
  }
459
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
+ }
460
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
461
743
  static void environment_mark(Environment* environment) {
462
- rb_gc_mark(environment->thread_txn_hash);
463
- rb_gc_mark(environment->txn_thread_hash);
744
+ rb_gc_mark(environment->thread_txn_hash);
745
+ rb_gc_mark(environment->txn_thread_hash);
464
746
  }
747
+ #endif
465
748
 
466
749
  /**
467
750
  * @overload close
@@ -473,35 +756,61 @@ static void environment_mark(Environment* environment) {
473
756
  * env.close
474
757
  */
475
758
  static VALUE environment_close(VALUE self) {
476
- ENVIRONMENT(self, environment);
477
- mdb_env_close(environment->env);
478
- environment->env = 0;
479
- 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);
480
789
  }
481
790
 
482
791
  static VALUE stat2hash(const MDB_stat* stat) {
483
- VALUE ret = rb_hash_new();
792
+ VALUE ret = rb_hash_new();
484
793
 
485
794
  #define STAT_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), INT2NUM(stat->ms_##name))
486
- STAT_SET(psize);
487
- STAT_SET(depth);
488
- STAT_SET(branch_pages);
489
- STAT_SET(leaf_pages);
490
- STAT_SET(overflow_pages);
491
- 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);
492
801
  #undef STAT_SET
493
802
 
494
- return ret;
803
+ return ret;
495
804
  }
496
805
 
497
806
  static VALUE flags2hash(int flags) {
498
- VALUE ret = rb_hash_new();
807
+ VALUE ret = rb_hash_new();
499
808
 
500
809
  #define FLAG(const, name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), (flags & MDB_##const) == 0 ? Qfalse : Qtrue);
501
810
  #include "dbi_flags.h"
502
811
  #undef FLAG
503
812
 
504
- return ret;
813
+ return ret;
505
814
  }
506
815
 
507
816
  /**
@@ -516,10 +825,10 @@ static VALUE flags2hash(int flags) {
516
825
  * * +:entries+ Number of data items
517
826
  */
518
827
  static VALUE environment_stat(VALUE self) {
519
- ENVIRONMENT(self, environment);
520
- MDB_stat stat;
521
- check(mdb_env_stat(environment->env, &stat));
522
- 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);
523
832
  }
524
833
 
525
834
  /**
@@ -534,23 +843,23 @@ static VALUE environment_stat(VALUE self) {
534
843
  * * +:numreaders+ Max readers slots in the environment
535
844
  */
536
845
  static VALUE environment_info(VALUE self) {
537
- MDB_envinfo info;
846
+ MDB_envinfo info;
538
847
 
539
- ENVIRONMENT(self, environment);
540
- check(mdb_env_info(environment->env, &info));
848
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
849
+ check(mdb_env_info(environment->env, &info));
541
850
 
542
- VALUE ret = rb_hash_new();
851
+ VALUE ret = rb_hash_new();
543
852
 
544
853
  #define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), SIZET2NUM((size_t)info.me_##name));
545
- INFO_SET(mapaddr);
546
- INFO_SET(mapsize);
547
- INFO_SET(last_pgno);
548
- INFO_SET(last_txnid);
549
- INFO_SET(maxreaders);
550
- 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);
551
860
  #undef INFO_SET
552
861
 
553
- return ret;
862
+ return ret;
554
863
  }
555
864
 
556
865
  /**
@@ -566,10 +875,10 @@ static VALUE environment_info(VALUE self) {
566
875
  * @raise [Error] when there is an error creating the copy.
567
876
  */
568
877
  static VALUE environment_copy(VALUE self, VALUE path) {
569
- ENVIRONMENT(self, environment);
570
- VALUE expanded_path = rb_file_expand_path(path, Qnil);
571
- check(mdb_env_copy(environment->env, StringValueCStr(expanded_path)));
572
- 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;
573
882
  }
574
883
 
575
884
  /**
@@ -586,37 +895,37 @@ static VALUE environment_copy(VALUE self, VALUE path) {
586
895
  * asynchronous.
587
896
  */
588
897
  static VALUE environment_sync(int argc, VALUE *argv, VALUE self) {
589
- ENVIRONMENT(self, environment);
898
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
590
899
 
591
- VALUE force;
592
- rb_scan_args(argc, argv, "01", &force);
900
+ VALUE force;
901
+ rb_scan_args(argc, argv, "01", &force);
593
902
 
594
- check(mdb_env_sync(environment->env, RTEST(force)));
595
- return Qnil;
903
+ check(mdb_env_sync(environment->env, RTEST(force)));
904
+ return Qnil;
596
905
  }
597
906
 
598
907
  static int environment_options(VALUE key, VALUE value, EnvironmentOptions* options) {
599
- ID id = rb_to_id(key);
908
+ ID id = rb_to_id(key);
600
909
 
601
- if (id == rb_intern("mode"))
602
- options->mode = NUM2INT(value);
603
- else if (id == rb_intern("maxreaders"))
604
- options->maxreaders = NUM2INT(value);
605
- else if (id == rb_intern("maxdbs"))
606
- options->maxdbs = NUM2INT(value);
607
- else if (id == rb_intern("mapsize"))
608
- 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);
609
918
 
610
919
  #define FLAG(const, name) else if (id == rb_intern(#name)) { if (RTEST(value)) { options->flags |= MDB_##const; } }
611
920
  #include "env_flags.h"
612
921
  #undef FLAG
613
922
 
614
- else {
615
- VALUE s = rb_inspect(key);
616
- rb_raise(cError, "Invalid option %s", StringValueCStr(s));
617
- }
923
+ else {
924
+ VALUE s = rb_inspect(key);
925
+ rb_raise(cError, "Invalid option %s", StringValueCStr(s));
926
+ }
618
927
 
619
- return 0;
928
+ return 0;
620
929
  }
621
930
 
622
931
  /**
@@ -654,50 +963,52 @@ static int environment_options(VALUE key, VALUE value, EnvironmentOptions* optio
654
963
  * end
655
964
  */
656
965
  static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
657
- VALUE path, option_hash;
966
+ VALUE path, option_hash;
658
967
 
659
968
  #ifdef RB_SCAN_ARGS_KEYWORDS
660
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
661
- argc, argv, "1:", &path, &option_hash);
969
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
970
+ argc, argv, "1:", &path, &option_hash);
662
971
  #else
663
- rb_scan_args(argc, argv, "1:", &path, &option_hash);
972
+ rb_scan_args(argc, argv, "1:", &path, &option_hash);
664
973
  #endif
665
974
 
666
- EnvironmentOptions options = {
667
- .flags = MDB_NOTLS,
668
- .maxreaders = -1,
669
- .maxdbs = 128,
670
- .mapsize = 0,
671
- .mode = 0755,
672
- };
673
- if (!NIL_P(option_hash))
674
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))environment_options,
675
- (VALUE)&options);
676
-
677
- MDB_env* env;
678
- check(mdb_env_create(&env));
679
-
680
- Environment* environment;
681
- VALUE venv = Data_Make_Struct(cEnvironment, Environment, environment_mark,
682
- environment_free, environment);
683
- environment->env = env;
684
- environment->thread_txn_hash = rb_hash_new();
685
- environment->txn_thread_hash = rb_hash_new();
686
-
687
- if (options.maxreaders > 0)
688
- check(mdb_env_set_maxreaders(env, options.maxreaders));
689
- if (options.mapsize > 0)
690
- check(mdb_env_set_mapsize(env, options.mapsize));
691
-
692
- check(mdb_env_set_maxdbs(env, options.maxdbs <= 0 ? 1 : options.maxdbs));
693
- VALUE expanded_path = rb_file_expand_path(path, Qnil);
694
- check(mdb_env_open(env, StringValueCStr(expanded_path), options.flags,
695
- options.mode));
696
-
697
- if (rb_block_given_p())
698
- return rb_ensure(rb_yield, venv, environment_close, venv);
699
-
700
- 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;
701
1012
  }
702
1013
 
703
1014
  /**
@@ -718,16 +1029,16 @@ static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
718
1029
  * env.flags #=> [:writemap, :nometasync]
719
1030
  */
720
1031
  static VALUE environment_flags(VALUE self) {
721
- unsigned int flags;
722
- ENVIRONMENT(self, environment);
723
- 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));
724
1035
 
725
- VALUE ret = rb_ary_new();
1036
+ VALUE ret = rb_ary_new();
726
1037
  #define FLAG(const, name) if (flags & MDB_##const) rb_ary_push(ret, ID2SYM(rb_intern(#name)));
727
1038
  #include "env_flags.h"
728
1039
  #undef FLAG
729
1040
 
730
- return ret;
1041
+ return ret;
731
1042
  }
732
1043
 
733
1044
  /**
@@ -736,33 +1047,33 @@ static VALUE environment_flags(VALUE self) {
736
1047
  * @return [String] the path that was used to open the environment.
737
1048
  */
738
1049
  static VALUE environment_path(VALUE self) {
739
- const char* path;
740
- ENVIRONMENT(self, environment);
741
- check(mdb_env_get_path(environment->env, &path));
742
- 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);
743
1054
  }
744
1055
 
745
1056
  static VALUE environment_set_mapsize(VALUE self, VALUE size) {
746
- ENVIRONMENT(self, environment);
747
- check(mdb_env_set_mapsize(environment->env, NUM2LONG(size)));
748
- return Qnil;
1057
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
1058
+ check(mdb_env_set_mapsize(environment->env, NUM2LONG(size)));
1059
+ return Qnil;
749
1060
  }
750
1061
 
751
1062
  static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set) {
752
- ENVIRONMENT(self, environment);
1063
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
753
1064
 
754
- int i;
755
- for (i = 0; i < argc; ++i) {
756
- 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]);
757
1068
 
758
- if (0) {}
1069
+ if (0) {}
759
1070
  #define FLAG(const, name) else if (id == rb_intern(#name)) check(mdb_env_set_flags(environment->env, MDB_##const, set));
760
1071
  #include "env_flags.h"
761
1072
  #undef FLAG
762
- else
763
- rb_raise(cError, "Invalid option %s", StringValueCStr(argv[i]));
764
- }
765
- return Qnil;
1073
+ else
1074
+ rb_raise(cError, "Invalid option %s", StringValueCStr(argv[i]));
1075
+ }
1076
+ return Qnil;
766
1077
  }
767
1078
 
768
1079
  /**
@@ -776,8 +1087,8 @@ static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set
776
1087
  * env.set_flags :nosync, :writemap
777
1088
  */
778
1089
  static VALUE environment_set_flags(int argc, VALUE* argv, VALUE self) {
779
- environment_change_flags(argc, argv, self, 1);
780
- return Qnil;
1090
+ environment_change_flags(argc, argv, self, 1);
1091
+ return Qnil;
781
1092
  }
782
1093
 
783
1094
  /**
@@ -791,8 +1102,8 @@ static VALUE environment_set_flags(int argc, VALUE* argv, VALUE self) {
791
1102
  * env.clear_flags :nosync, :writemap
792
1103
  */
793
1104
  static VALUE environment_clear_flags(int argc, VALUE* argv, VALUE self) {
794
- environment_change_flags(argc, argv, self, 0);
795
- return Qnil;
1105
+ environment_change_flags(argc, argv, self, 0);
1106
+ return Qnil;
796
1107
  }
797
1108
 
798
1109
  /**
@@ -805,32 +1116,57 @@ static VALUE environment_clear_flags(int argc, VALUE* argv, VALUE self) {
805
1116
  * end
806
1117
  */
807
1118
  static VALUE environment_active_txn(VALUE self) {
808
- ENVIRONMENT(self, environment);
809
- 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());
810
1121
  }
811
1122
 
812
1123
  static void environment_set_active_txn(VALUE self, VALUE thread, VALUE txn) {
813
- ENVIRONMENT(self, environment);
814
-
815
- if (NIL_P(txn)) {
816
- VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
817
- if (!NIL_P(oldtxn)) {
818
- rb_hash_delete(environment->thread_txn_hash, thread);
819
- rb_hash_delete(environment->txn_thread_hash, oldtxn);
820
- }
821
- } else {
822
- VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
823
- if (!NIL_P(oldtxn)) {
824
- rb_hash_delete(environment->txn_thread_hash, oldtxn);
825
- }
826
- rb_hash_aset(environment->txn_thread_hash, txn, thread);
827
- 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);
828
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
+ }
829
1165
  }
830
1166
 
831
1167
  static MDB_txn* extract_txn(VALUE vtxn) {
832
1168
  if (NIL_P(vtxn)) return NULL;
833
- TRANSACTION(vtxn, transaction);
1169
+ TRANSACTION(vtxn, &lmdb_transaction_type, transaction);
834
1170
  if (!transaction->txn) rb_raise(cError, "Transaction is already terminated");
835
1171
  if (transaction->thread != rb_thread_current())
836
1172
  rb_raise(cError, "Transaction is from another thread");
@@ -838,15 +1174,15 @@ static MDB_txn* extract_txn(VALUE vtxn) {
838
1174
  }
839
1175
 
840
1176
  static MDB_txn* active_txn(VALUE self) {
841
- VALUE vtxn = environment_active_txn(self);
842
- return extract_txn(vtxn);
1177
+ VALUE vtxn = environment_active_txn(self);
1178
+ return extract_txn(vtxn);
843
1179
  }
844
1180
 
845
1181
  static MDB_txn* need_txn(VALUE self) {
846
- MDB_txn* txn = active_txn(self);
1182
+ MDB_txn* txn = active_txn(self);
847
1183
 
848
- if (!txn) rb_raise(cError, "No active transaction");
849
- return txn;
1184
+ if (!txn) rb_raise(cError, "No active transaction");
1185
+ return txn;
850
1186
  }
851
1187
 
852
1188
  /**
@@ -883,19 +1219,59 @@ static MDB_txn* need_txn(VALUE self) {
883
1219
  * db['a'] #=> 3
884
1220
  */
885
1221
  static VALUE environment_transaction(int argc, VALUE *argv, VALUE self) {
886
- rb_need_block();
1222
+ rb_need_block();
887
1223
 
888
- VALUE readonly;
889
- rb_scan_args(argc, argv, "01", &readonly);
890
- 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;
891
1227
 
892
- return with_transaction(self, rb_yield, Qnil, flags);
1228
+ return with_transaction(self, rb_yield, Qnil, flags);
893
1229
  }
894
1230
 
895
- static void database_mark(Database* database) {
896
- rb_gc_mark(database->env);
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);
897
1236
  }
898
1237
 
1238
+ static void database_compact(void *ptr) {
1239
+ Database *database = (Database *)ptr;
1240
+ if (database)
1241
+ GC_LOCATION(database->env);
1242
+ }
1243
+
1244
+ static void database_free(void* ptr) {
1245
+ Database *database = (Database *)ptr;
1246
+ xfree(database);
1247
+ }
1248
+
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);
1272
+ }
1273
+ #endif
1274
+
899
1275
  #define METHOD database_flags
900
1276
  #define FILE "dbi_flags.h"
901
1277
  #include "flag_parser.h"
@@ -913,7 +1289,7 @@ static void database_mark(Database* database) {
913
1289
  * database aborts, the database is not created.
914
1290
  * @return [Database] newly-opened database
915
1291
  * @raise [Error] if there is an error opening the database
916
- * @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.
917
1293
  * @param [Hash] options Options for the database.
918
1294
  * @option options [Boolean] :reversekey Keys are strings to be
919
1295
  * compared in reverse order, from the end of the strings to the
@@ -940,35 +1316,64 @@ static void database_mark(Database* database) {
940
1316
  * transaction or a read-only environment.
941
1317
  */
942
1318
  static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
943
- ENVIRONMENT(self, environment);
944
- if (!active_txn(self))
945
- return call_with_transaction(self, self, "database", argc, argv, 0);
1319
+ ENVIRONMENT(self, &lmdb_environment_type, environment);
946
1320
 
947
- VALUE name, option_hash;
948
- #ifdef RB_SCAN_ARGS_KEYWORDS
949
- rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS,
950
- argc, argv, "01:", &name, &option_hash);
951
- #else
952
- rb_scan_args(argc, argv, "01:", &name, &option_hash);
953
- #endif
1321
+ if (!active_txn(self))
1322
+ return call_with_transaction(self, self, "database", argc, argv, 0);
954
1323
 
1324
+ VALUE name = Qnil;
1325
+ VALUE option_hash = Qnil;
955
1326
 
956
- int flags = 0;
957
- if (!NIL_P(option_hash))
958
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_flags,
959
- (VALUE)&flags);
1327
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1328
+ argc, argv, "01:", &name, &option_hash);
960
1329
 
961
- MDB_dbi dbi;
962
- check(mdb_dbi_open(need_txn(self), NIL_P(name) ? 0 : StringValueCStr(name),
963
- 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);
1334
+
1335
+ MDB_dbi dbi;
1336
+
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
+ }
964
1343
 
965
- Database* database;
966
- VALUE vdb = Data_Make_Struct(cDatabase, Database, database_mark, free,
967
- database);
968
- database->dbi = dbi;
969
- database->env = self;
1344
+ check(mdb_dbi_open(need_txn(self), NIL_P(name) ? 0 : StringValueCStr(name),
1345
+ flags, &dbi));
970
1346
 
971
- return vdb;
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);
972
1377
  }
973
1378
 
974
1379
  /**
@@ -980,42 +1385,42 @@ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
980
1385
  * @raise [Error] If there is an error opening the main database.
981
1386
  */
982
1387
  static VALUE environment_databases(VALUE self) {
983
- ENVIRONMENT(self, environment);
984
- if (!active_txn(self))
985
- return call_with_transaction(self, self, "databases", 0, 0, MDB_RDONLY);
986
-
987
- MDB_dbi dbi;
988
- MDB_cursor *cursor;
989
- MDB_txn *txn = need_txn(self);
990
- MDB_val key;
991
-
992
- check(mdb_dbi_open(txn, NULL, 0, &dbi));
993
- check(mdb_cursor_open(txn, dbi, &cursor));
994
-
995
- VALUE ret = rb_ary_new();
996
- while (mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP) == MDB_SUCCESS) {
997
- char *intern_db_name;
998
- MDB_dbi db;
999
- VALUE db_name;
1000
-
1001
- if (memchr(key.mv_data, '\0', key.mv_size))
1002
- continue;
1003
-
1004
- intern_db_name = malloc(key.mv_size + 1);
1005
- memcpy(intern_db_name, key.mv_data, key.mv_size);
1006
- intern_db_name[key.mv_size] = '\0';
1007
-
1008
- if (mdb_dbi_open(txn, intern_db_name, 0, &db) == MDB_SUCCESS) {
1009
- mdb_dbi_close(environment->env, db);
1010
- db_name = rb_str_new(key.mv_data, key.mv_size);
1011
- rb_ary_push(ret, db_name);
1012
- }
1013
- 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);
1014
1417
  }
1418
+ xfree(intern_db_name);
1419
+ }
1015
1420
 
1016
- mdb_cursor_close(cursor);
1421
+ mdb_cursor_close(cursor);
1017
1422
 
1018
- return ret;
1423
+ return ret;
1019
1424
  }
1020
1425
 
1021
1426
  /**
@@ -1030,14 +1435,14 @@ static VALUE environment_databases(VALUE self) {
1030
1435
  * * +:entries+ Number of data items
1031
1436
  */
1032
1437
  static VALUE database_stat(VALUE self) {
1033
- DATABASE(self, database);
1034
- if (!active_txn(database->env))
1035
- return call_with_transaction(database->env,
1036
- 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);
1037
1442
 
1038
- MDB_stat stat;
1039
- check(mdb_stat(need_txn(database->env), database->dbi, &stat));
1040
- return stat2hash(&stat);
1443
+ MDB_stat stat;
1444
+ check(mdb_stat(need_txn(database->env), database->dbi, &stat));
1445
+ return stat2hash(&stat);
1041
1446
  }
1042
1447
 
1043
1448
  /**
@@ -1046,13 +1451,13 @@ static VALUE database_stat(VALUE self) {
1046
1451
  * @return [Hash] The flags.
1047
1452
  */
1048
1453
  static VALUE database_get_flags(VALUE self) {
1049
- DATABASE(self, database);
1050
- if (!active_txn(database->env))
1051
- return call_with_transaction(database->env,
1052
- self, "flags", 0, 0, MDB_RDONLY);
1053
- unsigned int flags;
1054
- check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
1055
- 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);
1056
1461
  }
1057
1462
 
1058
1463
  /* XXX these two could probably also be macro'd, or maybe not i dunno */
@@ -1063,14 +1468,14 @@ static VALUE database_get_flags(VALUE self) {
1063
1468
  * @return [true, false]
1064
1469
  */
1065
1470
  static VALUE database_is_dupsort(VALUE self) {
1066
- DATABASE(self, database);
1067
- if (!active_txn(database->env))
1068
- return call_with_transaction(database->env, self,
1069
- "dupsort?", 0, 0, MDB_RDONLY);
1070
- unsigned int flags;
1071
- 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));
1072
1477
 
1073
- return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
1478
+ return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
1074
1479
  }
1075
1480
 
1076
1481
  /**
@@ -1079,14 +1484,14 @@ static VALUE database_is_dupsort(VALUE self) {
1079
1484
  * @return [true, false]
1080
1485
  */
1081
1486
  static VALUE database_is_dupfixed(VALUE self) {
1082
- DATABASE(self, database);
1083
- if (!active_txn(database->env))
1084
- return call_with_transaction(database->env, self,
1085
- "dupfixed?", 0, 0, MDB_RDONLY);
1086
- unsigned int flags;
1087
- 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));
1088
1493
 
1089
- return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
1494
+ return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
1090
1495
  }
1091
1496
 
1092
1497
  /**
@@ -1096,11 +1501,11 @@ static VALUE database_is_dupfixed(VALUE self) {
1096
1501
  * @note The drop happens transactionally.
1097
1502
  */
1098
1503
  static VALUE database_drop(VALUE self) {
1099
- DATABASE(self, database);
1100
- if (!active_txn(database->env))
1101
- return call_with_transaction(database->env, self, "drop", 0, 0, 0);
1102
- check(mdb_drop(need_txn(database->env), database->dbi, 1));
1103
- 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;
1104
1509
  }
1105
1510
 
1106
1511
  /**
@@ -1110,11 +1515,11 @@ static VALUE database_drop(VALUE self) {
1110
1515
  * @note The clear happens transactionally.
1111
1516
  */
1112
1517
  static VALUE database_clear(VALUE self) {
1113
- DATABASE(self, database);
1114
- if (!active_txn(database->env))
1115
- return call_with_transaction(database->env, self, "clear", 0, 0, 0);
1116
- check(mdb_drop(need_txn(database->env), database->dbi, 0));
1117
- 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;
1118
1523
  }
1119
1524
 
1120
1525
  /**
@@ -1127,20 +1532,23 @@ static VALUE database_clear(VALUE self) {
1127
1532
  * @param key The key of the record to retrieve.
1128
1533
  */
1129
1534
  static VALUE database_get(VALUE self, VALUE vkey) {
1130
- DATABASE(self, database);
1131
- if (!active_txn(database->env))
1132
- 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
+
1133
1539
 
1134
- vkey = StringValue(vkey);
1135
- MDB_val key, value;
1136
- key.mv_size = RSTRING_LEN(vkey);
1137
- key.mv_data = RSTRING_PTR(vkey);
1540
+ vkey = StringValue(vkey);
1541
+ MDB_val key, value;
1542
+ key.mv_size = RSTRING_LEN(vkey);
1543
+ key.mv_data = RSTRING_PTR(vkey);
1138
1544
 
1139
- int ret = mdb_get(need_txn(database->env), database->dbi, &key, &value);
1140
- if (ret == MDB_NOTFOUND)
1141
- return Qnil;
1142
- check(ret);
1143
- return rb_str_new(value.mv_data, value.mv_size);
1545
+ // rb_warn("lol database get %s", (char *)key.mv_data);
1546
+
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);
1144
1552
  }
1145
1553
 
1146
1554
  #define METHOD database_put_flags
@@ -1177,34 +1585,34 @@ static VALUE database_get(VALUE self, VALUE vkey) {
1177
1585
  * data.
1178
1586
  */
1179
1587
  static VALUE database_put(int argc, VALUE *argv, VALUE self) {
1180
- DATABASE(self, database);
1181
- if (!active_txn(database->env))
1182
- 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);
1183
1591
 
1184
- VALUE vkey, vval, option_hash = Qnil;
1592
+ VALUE vkey, vval, option_hash = Qnil;
1185
1593
  #ifdef RB_SCAN_ARGS_KEYWORDS
1186
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1187
- 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);
1188
1596
  #else
1189
- rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
1597
+ rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
1190
1598
  #endif
1191
1599
 
1192
- int flags = 0;
1193
- if (!NIL_P(option_hash))
1194
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_put_flags,
1195
- (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);
1196
1604
 
1197
- vkey = StringValue(vkey);
1198
- vval = StringValue(vval);
1605
+ vkey = StringValue(vkey);
1606
+ vval = StringValue(vval);
1199
1607
 
1200
- MDB_val key, value;
1201
- key.mv_size = RSTRING_LEN(vkey);
1202
- key.mv_data = RSTRING_PTR(vkey);
1203
- value.mv_size = RSTRING_LEN(vval);
1204
- 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);
1205
1613
 
1206
- check(mdb_put(need_txn(database->env), database->dbi, &key, &value, flags));
1207
- return Qnil;
1614
+ check(mdb_put(need_txn(database->env), database->dbi, &key, &value, flags));
1615
+ return Qnil;
1208
1616
  }
1209
1617
 
1210
1618
  /**
@@ -1223,59 +1631,97 @@ static VALUE database_put(int argc, VALUE *argv, VALUE self) {
1223
1631
  * @raise [Error] if the specified key/value pair is not in the database.
1224
1632
  */
1225
1633
  static VALUE database_delete(int argc, VALUE *argv, VALUE self) {
1226
- DATABASE(self, database);
1227
- if (!active_txn(database->env))
1228
- return call_with_transaction(database->env, self, "delete", argc, argv, 0);
1229
-
1230
- VALUE vkey, vval;
1231
- rb_scan_args(argc, argv, "11", &vkey, &vval);
1232
-
1233
- vkey = StringValue(vkey);
1234
-
1235
- MDB_val key;
1236
- key.mv_size = RSTRING_LEN(vkey);
1237
- key.mv_data = RSTRING_PTR(vkey);
1238
-
1239
- if (NIL_P(vval)) {
1240
- check(mdb_del(need_txn(database->env), database->dbi, &key, 0));
1241
- } else {
1242
- vval = StringValue(vval);
1243
- MDB_val value;
1244
- value.mv_size = RSTRING_LEN(vval);
1245
- value.mv_data = RSTRING_PTR(vval);
1246
- check(mdb_del(need_txn(database->env), database->dbi, &key, &value));
1247
- }
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);
1248
1637
 
1249
- return Qnil;
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);
1646
+
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;
1250
1658
  }
1251
1659
 
1252
- static void cursor_free(Cursor* cursor) {
1253
- if (cursor->cur) {
1254
- rb_warn("Memory leak - Garbage collecting open cursor");
1255
- // mdb_cursor_close(cursor->cur);
1256
- }
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
+ }
1257
1666
 
1258
- free(cursor);
1667
+ xfree(cursor);
1259
1668
  }
1260
1669
 
1261
1670
  static void cursor_check(Cursor* cursor) {
1262
- if (!cursor->cur)
1263
- rb_raise(cError, "Cursor is closed");
1264
- }
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
+ */
1265
1707
 
1708
+ #else
1266
1709
  static void cursor_mark(Cursor* cursor) {
1267
- rb_gc_mark(cursor->db);
1710
+ rb_gc_mark(cursor->db);
1268
1711
  }
1712
+ #endif
1269
1713
 
1270
1714
  /**
1271
1715
  * @overload close
1272
1716
  * Close a cursor. The cursor must not be used again after this call.
1273
1717
  */
1274
1718
  static VALUE cursor_close(VALUE self) {
1275
- CURSOR(self, cursor);
1276
- mdb_cursor_close(cursor->cur);
1277
- cursor->cur = 0;
1278
- 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;
1279
1725
  }
1280
1726
 
1281
1727
  /**
@@ -1296,44 +1742,45 @@ static VALUE cursor_close(VALUE self) {
1296
1742
  * end
1297
1743
  */
1298
1744
  static VALUE database_cursor(VALUE self) {
1299
- DATABASE(self, database);
1300
- if (!active_txn(database->env)) {
1301
- if (!rb_block_given_p()) {
1302
- rb_raise(cError, "Must call with block or active transaction.");
1303
- }
1304
- return call_with_transaction(database->env, self, "cursor", 0, 0, 0);
1305
- }
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
+ }
1306
1752
 
1307
- MDB_cursor* cur;
1308
- check(mdb_cursor_open(need_txn(database->env), database->dbi, &cur));
1309
-
1310
- Cursor* cursor;
1311
- VALUE vcur = Data_Make_Struct(cCursor, Cursor, cursor_mark, cursor_free, cursor);
1312
- cursor->cur = cur;
1313
- cursor->db = self;
1314
-
1315
- if (rb_block_given_p()) {
1316
- int exception;
1317
- VALUE ret = rb_protect(rb_yield, vcur, &exception);
1318
- if (exception) {
1319
- cursor_close(vcur);
1320
- rb_jump_tag(exception);
1321
- }
1322
- cursor_close(vcur);
1323
- return ret;
1324
- }
1325
- else {
1326
- VALUE vtxn = environment_active_txn(database->env);
1327
- if (NIL_P(vtxn)) {
1328
- rb_fatal("Internal error: transaction finished unexpectedly.");
1329
- }
1330
- else {
1331
- TRANSACTION(vtxn, txn);
1332
- rb_ary_push(txn->cursors, vcur);
1333
- }
1334
- }
1753
+ MDB_cursor* cur;
1754
+ check(mdb_cursor_open(need_txn(database->env), database->dbi, &cur));
1755
+
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
+ }
1335
1782
 
1336
- return vcur;
1783
+ return vcur;
1337
1784
  }
1338
1785
 
1339
1786
  /**
@@ -1341,8 +1788,8 @@ static VALUE database_cursor(VALUE self) {
1341
1788
  * @return [Environment] the environment to which this database belongs.
1342
1789
  */
1343
1790
  static VALUE database_env(VALUE self) {
1344
- DATABASE(self, database);
1345
- return database->env;
1791
+ DATABASE(self, &lmdb_database_type, database);
1792
+ return database->env;
1346
1793
  }
1347
1794
 
1348
1795
  /**
@@ -1353,11 +1800,11 @@ static VALUE database_env(VALUE self) {
1353
1800
  * nil if no record
1354
1801
  */
1355
1802
  static VALUE cursor_first(VALUE self) {
1356
- CURSOR(self, cursor);
1357
- MDB_val key, value;
1803
+ CURSOR(self, &lmdb_cursor_type, cursor);
1804
+ MDB_val key, value;
1358
1805
 
1359
- check(mdb_cursor_get(cursor->cur, &key, &value, MDB_FIRST));
1360
- 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));
1361
1808
  }
1362
1809
 
1363
1810
  /**
@@ -1368,11 +1815,11 @@ static VALUE cursor_first(VALUE self) {
1368
1815
  * nil if no record.
1369
1816
  */
1370
1817
  static VALUE cursor_last(VALUE self) {
1371
- CURSOR(self, cursor);
1372
- MDB_val key, value;
1818
+ CURSOR(self, &lmdb_cursor_type, cursor);
1819
+ MDB_val key, value;
1373
1820
 
1374
- check(mdb_cursor_get(cursor->cur, &key, &value, MDB_LAST));
1375
- 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));
1376
1823
  }
1377
1824
 
1378
1825
  /**
@@ -1383,14 +1830,14 @@ static VALUE cursor_last(VALUE self) {
1383
1830
  * nil if no previous record.
1384
1831
  */
1385
1832
  static VALUE cursor_prev(VALUE self) {
1386
- CURSOR(self, cursor);
1387
- MDB_val key, value;
1833
+ CURSOR(self, &lmdb_cursor_type, cursor);
1834
+ MDB_val key, value;
1388
1835
 
1389
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_PREV);
1390
- if (ret == MDB_NOTFOUND)
1391
- return Qnil;
1392
- check(ret);
1393
- 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));
1394
1841
  }
1395
1842
 
1396
1843
  /**
@@ -1402,22 +1849,22 @@ static VALUE cursor_prev(VALUE self) {
1402
1849
  * nil if no next record.
1403
1850
  */
1404
1851
  static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
1405
- CURSOR(self, cursor);
1406
- VALUE nodup;
1407
- MDB_val key, value;
1408
- 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;
1409
1856
 
1410
- rb_scan_args(argc, argv, "01", &nodup);
1857
+ rb_scan_args(argc, argv, "01", &nodup);
1411
1858
 
1412
- if (RTEST(nodup))
1413
- op = MDB_NEXT_NODUP;
1859
+ if (RTEST(nodup))
1860
+ op = MDB_NEXT_NODUP;
1414
1861
 
1415
- int ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1416
- if (ret == MDB_NOTFOUND)
1417
- return Qnil;
1418
- check(ret);
1419
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1420
- 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));
1421
1868
  }
1422
1869
 
1423
1870
  /**
@@ -1430,26 +1877,26 @@ static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
1430
1877
  * nil if no next record or the next record is out of the range.
1431
1878
  */
1432
1879
  static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1433
- CURSOR(self, cursor);
1434
- MDB_val key, value, ub_key;
1880
+ CURSOR(self, &lmdb_cursor_type, cursor);
1881
+ MDB_val key, value, ub_key;
1435
1882
 
1436
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_NEXT);
1437
- if (ret == MDB_NOTFOUND)
1438
- return Qnil;
1439
- 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);
1440
1887
 
1441
- ub_key.mv_size = RSTRING_LEN(upper_bound_key);
1442
- 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);
1443
1890
 
1444
- MDB_txn* txn = mdb_cursor_txn(cursor->cur);
1445
- 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);
1446
1893
 
1447
- if (mdb_cmp(txn, dbi, &key, &ub_key) <= 0) {
1448
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1449
- rb_str_new(value.mv_data, value.mv_size));
1450
- } else {
1451
- return Qnil;
1452
- }
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
+ }
1453
1900
  }
1454
1901
 
1455
1902
  /**
@@ -1460,46 +1907,46 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1460
1907
  * @param value [nil, #to_s] The optional value (+:dupsort+ only)
1461
1908
  * @return [Array] The +[key, value]+ pair to which the cursor now points.
1462
1909
  */
1463
- static VALUE cursor_set(int argc, VALUE* argv, VALUE self) {
1464
- CURSOR(self, cursor);
1465
- VALUE vkey, vval;
1466
- MDB_val key, value;
1467
- MDB_cursor_op op = MDB_SET_KEY;
1468
- 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;
1469
1916
 
1470
- rb_scan_args(argc, argv, "11", &vkey, &vval);
1917
+ rb_scan_args(argc, argv, "11", &vkey, &vval);
1471
1918
 
1472
- /*
1473
- XXX TODO: this was a nasty segfault: the key (and any
1474
- non-nil value) should be asserted to be strings, but then
1475
- if the database is `integerkeys` then perhaps we should
1476
- coerce?
1477
- */
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
+ */
1478
1925
 
1479
- if (TYPE(vkey) != T_STRING)
1480
- 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");
1481
1928
 
1482
- key.mv_size = RSTRING_LEN(vkey);
1483
- key.mv_data = StringValuePtr(vkey);
1929
+ key.mv_size = RSTRING_LEN(vkey);
1930
+ key.mv_data = StringValuePtr(vkey);
1484
1931
 
1485
- if (!NIL_P(vval)) {
1486
- if (TYPE(vval) != T_STRING)
1487
- 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");
1488
1935
 
1489
- op = MDB_GET_BOTH;
1490
- value.mv_size = RSTRING_LEN(vval);
1491
- value.mv_data = StringValuePtr(vval);
1492
- }
1936
+ op = MDB_GET_BOTH;
1937
+ value.mv_size = RSTRING_LEN(vval);
1938
+ value.mv_data = StringValuePtr(vval);
1939
+ }
1493
1940
 
1494
- ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1941
+ ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1495
1942
 
1496
- if (!NIL_P(vval) && ret == MDB_NOTFOUND)
1497
- return Qnil;
1943
+ if (!NIL_P(vval) && ret == MDB_NOTFOUND)
1944
+ return Qnil;
1498
1945
 
1499
- check(ret);
1946
+ check(ret);
1500
1947
 
1501
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1502
- 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));
1503
1950
  }
1504
1951
 
1505
1952
  /**
@@ -1509,22 +1956,22 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1509
1956
  * @return [Array] The [key, value] pair to which the cursor now points.
1510
1957
  */
1511
1958
  static VALUE cursor_set_range(VALUE self, VALUE vkey) {
1512
- CURSOR(self, cursor);
1513
- MDB_val key, value;
1514
- int ret;
1959
+ CURSOR(self, &lmdb_cursor_type, cursor);
1960
+ MDB_val key, value;
1961
+ int ret;
1515
1962
 
1516
- key.mv_size = RSTRING_LEN(vkey);
1517
- key.mv_data = StringValuePtr(vkey);
1963
+ key.mv_size = RSTRING_LEN(vkey);
1964
+ key.mv_data = StringValuePtr(vkey);
1518
1965
 
1519
- ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
1966
+ ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
1520
1967
 
1521
- /* not sure why we were letting this throw an exception */
1522
- if (ret == MDB_NOTFOUND) return Qnil;
1968
+ /* not sure why we were letting this throw an exception */
1969
+ if (ret == MDB_NOTFOUND) return Qnil;
1523
1970
 
1524
- check(ret);
1971
+ check(ret);
1525
1972
 
1526
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1527
- 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));
1528
1975
  }
1529
1976
 
1530
1977
  /**
@@ -1534,14 +1981,14 @@ static VALUE cursor_set_range(VALUE self, VALUE vkey) {
1534
1981
  */
1535
1982
 
1536
1983
  static VALUE cursor_get(VALUE self) {
1537
- CURSOR(self, cursor);
1984
+ CURSOR(self, &lmdb_cursor_type, cursor);
1538
1985
 
1539
- MDB_val key, value;
1540
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_GET_CURRENT);
1541
- if (ret == MDB_NOTFOUND)
1542
- return Qnil;
1543
- check(ret);
1544
- 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));
1545
1992
  }
1546
1993
 
1547
1994
  #define METHOD cursor_put_flags
@@ -1582,32 +2029,32 @@ static VALUE cursor_get(VALUE self) {
1582
2029
  * data.
1583
2030
  */
1584
2031
  static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
1585
- CURSOR(self, cursor);
2032
+ CURSOR(self, &lmdb_cursor_type, cursor);
1586
2033
 
1587
- VALUE vkey, vval, option_hash;
2034
+ VALUE vkey, vval, option_hash;
1588
2035
  #ifdef RB_SCAN_ARGS_KEYWORDS
1589
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1590
- 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);
1591
2038
  #else
1592
- rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
2039
+ rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
1593
2040
  #endif
1594
2041
 
1595
- int flags = 0;
1596
- if (!NIL_P(option_hash))
1597
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))cursor_put_flags,
1598
- (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);
1599
2046
 
1600
- vkey = StringValue(vkey);
1601
- vval = StringValue(vval);
2047
+ vkey = StringValue(vkey);
2048
+ vval = StringValue(vval);
1602
2049
 
1603
- MDB_val key, value;
1604
- key.mv_size = RSTRING_LEN(vkey);
1605
- key.mv_data = RSTRING_PTR(vkey);
1606
- value.mv_size = RSTRING_LEN(vval);
1607
- 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);
1608
2055
 
1609
- check(mdb_cursor_put(cursor->cur, &key, &value, flags));
1610
- return Qnil;
2056
+ check(mdb_cursor_put(cursor->cur, &key, &value, flags));
2057
+ return Qnil;
1611
2058
  }
1612
2059
 
1613
2060
  #define METHOD cursor_delete_flags
@@ -1620,28 +2067,28 @@ static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
1620
2067
  * @overload delete(options)
1621
2068
  * Delete current key/data pair.
1622
2069
  * This function deletes the key/data pair to which the cursor refers.
1623
- * @option options [Boolean] :nodupdata Delete all of the data
2070
+ * @option options [Boolean] :nodupdata Delete all of the data
1624
2071
  * items for the current key. This flag may only be specified
1625
2072
  * if the database was opened with +:dupsort+.
1626
2073
  */
1627
2074
  static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
1628
- CURSOR(self, cursor);
2075
+ CURSOR(self, &lmdb_cursor_type, cursor);
1629
2076
 
1630
- VALUE option_hash;
2077
+ VALUE option_hash;
1631
2078
  #ifdef RB_SCAN_ARGS_KEYWORDS
1632
- rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
1633
- argc, argv, ":", &option_hash);
2079
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
2080
+ argc, argv, ":", &option_hash);
1634
2081
  #else
1635
- rb_scan_args(argc, argv, ":", &option_hash);
2082
+ rb_scan_args(argc, argv, ":", &option_hash);
1636
2083
  #endif
1637
2084
 
1638
- int flags = 0;
1639
- if (!NIL_P(option_hash))
1640
- rb_hash_foreach(option_hash, (int (*)(ANYARGS))cursor_delete_flags,
1641
- (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);
1642
2089
 
1643
- check(mdb_cursor_del(cursor->cur, flags));
1644
- return Qnil;
2090
+ check(mdb_cursor_del(cursor->cur, flags));
2091
+ return Qnil;
1645
2092
  }
1646
2093
 
1647
2094
  /**
@@ -1649,8 +2096,8 @@ static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
1649
2096
  * @return [Database] the database which this cursor is iterating over.
1650
2097
  */
1651
2098
  static VALUE cursor_db(VALUE self) {
1652
- CURSOR(self, cursor);
1653
- return cursor->db;
2099
+ CURSOR(self, &lmdb_cursor_type, cursor);
2100
+ return cursor->db;
1654
2101
  }
1655
2102
 
1656
2103
  /**
@@ -1661,231 +2108,251 @@ static VALUE cursor_db(VALUE self) {
1661
2108
  * @return [Number] count of duplicates
1662
2109
  */
1663
2110
  static VALUE cursor_count(VALUE self) {
1664
- CURSOR(self, cursor);
1665
- size_t count;
1666
- check(mdb_cursor_count(cursor->cur, &count));
1667
- 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);
1668
2115
  }
1669
2116
 
1670
2117
  void Init_lmdb_ext() {
1671
- VALUE mLMDB;
1672
-
1673
- /**
1674
- * Document-module: LMDB
1675
- *
1676
- * The LMDB module presents a Ruby API to the OpenLDAP Lightning Memory-mapped Database (LMDB).
1677
- * @see http://symas.com/mdb/
1678
- */
1679
- mLMDB = rb_define_module("LMDB");
1680
- rb_define_const(mLMDB, "LIB_VERSION", rb_str_new2(MDB_VERSION_STRING));
1681
- 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);
1682
2129
 
1683
2130
  #define VERSION_CONST(name) rb_define_const(mLMDB, "LIB_VERSION_"#name, INT2NUM(MDB_VERSION_##name));
1684
- VERSION_CONST(MAJOR)
1685
- VERSION_CONST(MINOR)
1686
- VERSION_CONST(PATCH)
2131
+ VERSION_CONST(MAJOR)
2132
+ VERSION_CONST(MINOR)
2133
+ VERSION_CONST(PATCH)
1687
2134
  #undef VERSION_CONST
1688
2135
 
1689
- /**
1690
- * Document-class: LMDB::Error
1691
- *
1692
- * A general class of exceptions raised within the LMDB gem.
1693
- */
1694
- cError = rb_define_class_under(mLMDB, "Error", rb_eRuntimeError);
1695
- #define ERROR(name) \
1696
- 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);
1697
2144
  #include "errors.h"
1698
2145
  #undef ERROR
1699
2146
 
1700
- /**
1701
- * Document-class: LMDB::Environment
1702
- *
1703
- * The Environment is the root object for all LMDB operations.
1704
- *
1705
- * An LMDB "environment" is a collection of one or more "databases"
1706
- * (key-value tables), along with transactions to modify those
1707
- * databases and cursors to iterate through them.
1708
- *
1709
- * An environment -- and its collection of databases -- is normally
1710
- * stored in a directory. That directory will contain two files:
1711
- * * +data.mdb+: all the records in all the databases in the environment
1712
- * * +lock.mdb+: state of transactions that may be going on in the environment.
1713
- *
1714
- * An environment can contain multiple databases. Each of the
1715
- * databases has a string name ("mydatabase", "db.3.1982"). You use
1716
- * the database name to open the database within the environment.
1717
- *
1718
- * @example The normal pattern for using LMDB in Ruby
1719
- * env = LMDB.new "databasedir"
1720
- * db = env.database "databasename"
1721
- * # ... do things to the database ...
1722
- * env.close
1723
- */
1724
- cEnvironment = rb_define_class_under(mLMDB, "Environment", rb_cObject);
1725
- rb_undef_alloc_func(cEnvironment);
1726
- rb_define_singleton_method(cEnvironment, "new", environment_new, -1);
1727
- rb_define_method(cEnvironment, "database", environment_database, -1);
1728
- rb_define_method(cEnvironment, "databases", environment_databases, 0);
1729
- rb_define_method(cEnvironment, "active_txn", environment_active_txn, 0);
1730
- rb_define_method(cEnvironment, "close", environment_close, 0);
1731
- rb_define_method(cEnvironment, "stat", environment_stat, 0);
1732
- rb_define_method(cEnvironment, "info", environment_info, 0);
1733
- rb_define_method(cEnvironment, "copy", environment_copy, 1);
1734
- rb_define_method(cEnvironment, "sync", environment_sync, -1);
1735
- rb_define_method(cEnvironment, "mapsize=", environment_set_mapsize, 1);
1736
- rb_define_method(cEnvironment, "set_flags", environment_set_flags, -1);
1737
- rb_define_method(cEnvironment, "clear_flags", environment_clear_flags, -1);
1738
- rb_define_method(cEnvironment, "flags", environment_flags, 0);
1739
- rb_define_method(cEnvironment, "path", environment_path, 0);
1740
- rb_define_method(cEnvironment, "transaction", environment_transaction, -1);
1741
-
1742
- /**
1743
- * Document-class: LMDB::Database
1744
- *
1745
- * An LMDB Database is a table of key-value pairs. It is stored as
1746
- * part of the {Environment}.
1747
- *
1748
- * By default, each key in a Database maps to one value. However, a
1749
- * Database can be configured at creation to allow duplicate keys, in
1750
- * which case one key will map to multiple values.
1751
- *
1752
- * A Database stores the keys in a sorted order. The order can also
1753
- * be set with options when the database is created.
1754
- *
1755
- * The basic operations on a database are to {#put}, {#get}, and
1756
- * {#delete} records. One can also iterate through the records in a
1757
- * database using a {Cursor}.
1758
- *
1759
- * @example Typical usage
1760
- * env = LMDB.new "databasedir"
1761
- * db = env.database "databasename"
1762
- * db.put "key1", "value1"
1763
- * db.put "key2", "value2"
1764
- * db.get "key1" #=> "value1"
1765
- * env.close
1766
- */
1767
- cDatabase = rb_define_class_under(mLMDB, "Database", rb_cObject);
1768
- rb_undef_alloc_func(cDatabase);
1769
- rb_undef_method(rb_singleton_class(cDatabase), "new");
1770
- rb_define_method(cDatabase, "stat", database_stat, 0);
1771
- rb_define_method(cDatabase, "flags", database_get_flags, 0);
1772
- rb_define_method(cDatabase, "dupsort?", database_is_dupsort, 0);
1773
- rb_define_method(cDatabase, "dupfixed?", database_is_dupfixed, 0);
1774
- rb_define_method(cDatabase, "drop", database_drop, 0);
1775
- rb_define_method(cDatabase, "clear", database_clear, 0);
1776
- rb_define_method(cDatabase, "get", database_get, 1);
1777
- rb_define_method(cDatabase, "put", database_put, -1);
1778
- rb_define_method(cDatabase, "delete", database_delete, -1);
1779
- rb_define_method(cDatabase, "cursor", database_cursor, 0);
1780
- rb_define_method(cDatabase, "env", database_env, 0);
1781
-
1782
- /**
1783
- * Document-class: LMDB::Transaction
1784
- *
1785
- * The LMDB environment supports transactional reads and updates. By
1786
- * default, these provide the standard ACID (atomicity, consistency,
1787
- * isolation, durability) behaviors.
1788
- *
1789
- * Transactions can be committed or aborted. When a transaction is
1790
- * committed, all its effects take effect in the database atomically.
1791
- * When a transaction is aborted, none of its effects take effect.
1792
- *
1793
- * Transactions span the entire environment. All the updates made in
1794
- * the course of an update transaction -- writing records across all
1795
- * databases, creating databases, and destroying databases -- are
1796
- * either completed atomically or rolled back.
1797
- *
1798
- * Transactions can be nested. A child transaction can be started
1799
- * within a parent transaction. The child transaction can commit or
1800
- * abort, at which point the effects of the child become visible to
1801
- * the parent transaction or not. If the parent aborts, all of the
1802
- * changes performed in the context of the parent -- including the
1803
- * changes from a committed child transaction -- are rolled back.
1804
- *
1805
- * To create a transaction, call {Environment#transaction} and supply
1806
- * a block for the code to execute in that transaction.
1807
- *
1808
- * @example Typical usage
1809
- * env = LMDB.new "databasedir"
1810
- * db1 = env.database "database1"
1811
- * env.transaction do |parent|
1812
- * db2 = env.database "database2", :create => true
1813
- * #=> creates a new database, but it isn't
1814
- * #=> yet committed to storage
1815
- * db1['x'] #=> nil
1816
- * env.transaction do |child1|
1817
- * db2['a'] = 'b'
1818
- * db1['x'] = 'y'
1819
- * end
1820
- * #=> first child transaction commits
1821
- * #=> changes are visible within the parent transaction
1822
- * #=> but are not yet permanent
1823
- * db1['x'] #=> 'y'
1824
- * db2['a'] #=> 'a'
1825
- * env.transaction do |child2|
1826
- * db2['a'] = 'def'
1827
- * db1['x'] = 'ghi'
1828
- * child2.abort
1829
- * #=> second child transaction aborts and rolls
1830
- * #=> back its changes
1831
- * end
1832
- * db1['x'] #=> 'y'
1833
- * db2['a'] #=> 'a'
1834
- * end
1835
- * #=> parent transaction commits and writes database2
1836
- * #=> and the updates from transaction child1 to
1837
- * #=> storage.
1838
- */
1839
- cTransaction = rb_define_class_under(mLMDB, "Transaction", rb_cObject);
1840
- rb_undef_alloc_func(cTransaction);
1841
- rb_undef_method(rb_singleton_class(cTransaction), "new");
1842
- rb_define_method(cTransaction, "commit", transaction_commit, 0);
1843
- rb_define_method(cTransaction, "abort", transaction_abort, 0);
1844
- rb_define_method(cTransaction, "env", transaction_env, 0);
1845
- rb_define_method(cTransaction, "readonly?", transaction_is_readonly, 0);
1846
- rb_define_method(cTransaction, "finished?", transaction_is_finished, 0);
1847
- rb_define_method(cTransaction, "error?", transaction_is_error, 0);
1848
-
1849
- /**
1850
- * Document-class: LMDB::Cursor
1851
- *
1852
- * A Cursor points to records in a database, and is used to iterate
1853
- * through the records in the database.
1854
- *
1855
- * Cursors are created in the context of a transaction, and should
1856
- * only be used as long as that transaction is active. In other words,
1857
- * after you {Transaction#commit} or {Transaction#abort} a transaction,
1858
- * the cursors created while that transaction was active are no longer
1859
- * usable.
1860
- *
1861
- * To create a cursor, call {Database#cursor} and pass it a block for
1862
- * that should be performed using the cursor.
1863
- *
1864
- * @example Typical usage
1865
- * env = LMDB.new "databasedir"
1866
- * db = env.database "databasename"
1867
- * db.cursor do |cursor|
1868
- * rl = cursor.last #=> content of the last record
1869
- * r1 = cursor.first #=> content of the first record
1870
- * r2 = cursor.next #=> content of the second record
1871
- * cursor.put "x", "y", current: true
1872
- * #=> replaces the second record with a new value "y"
1873
- * end
1874
- */
1875
- cCursor = rb_define_class_under(mLMDB, "Cursor", rb_cObject);
1876
- rb_undef_alloc_func(cCursor);
1877
- rb_undef_method(rb_singleton_class(cCursor), "new");
1878
- rb_define_method(cCursor, "close", cursor_close, 0);
1879
- rb_define_method(cCursor, "get", cursor_get, 0);
1880
- rb_define_method(cCursor, "first", cursor_first, 0);
1881
- rb_define_method(cCursor, "last", cursor_last, 0);
1882
- rb_define_method(cCursor, "next", cursor_next, -1);
1883
- rb_define_method(cCursor, "next_range", cursor_next_range, 1);
1884
- rb_define_method(cCursor, "prev", cursor_prev, 0);
1885
- rb_define_method(cCursor, "set", cursor_set, -1);
1886
- rb_define_method(cCursor, "set_range", cursor_set_range, 1);
1887
- rb_define_method(cCursor, "put", cursor_put, -1);
1888
- rb_define_method(cCursor, "count", cursor_count, 0);
1889
- rb_define_method(cCursor, "delete", cursor_delete, -1);
1890
- 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
+ */
1891
2358
  }