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.
- checksums.yaml +4 -4
- data/ext/lmdb_ext/extconf.rb +2 -0
- data/ext/lmdb_ext/lmdb_ext.c +1379 -912
- data/ext/lmdb_ext/lmdb_ext.h +36 -19
- data/lib/lmdb/version.rb +1 -1
- data/spec/gc_torture_spec.rb +162 -0
- data/spec/helper.rb +3 -3
- data/spec/lmdb_spec.rb +144 -137
- data/spec/pseudo_transactions_spec.rb +237 -0
- metadata +6 -2
data/ext/lmdb_ext/lmdb_ext.c
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
25
|
-
|
|
24
|
+
if (!code)
|
|
25
|
+
return;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
37
|
+
rb_raise(cError, "%s", err); /* fallback */
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
234
|
+
TRANSACTION(self, &lmdb_transaction_type, transaction);
|
|
235
|
+
|
|
236
|
+
if (!transaction->txn)
|
|
237
|
+
rb_raise(cError, "Transaction is already terminated");
|
|
165
238
|
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
262
|
+
rb_raise(cError, "Transaction is not active");
|
|
182
263
|
|
|
183
264
|
// now eliminate the cursors
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
check(mdb_txn_commit(transaction->txn));
|
|
195
276
|
else
|
|
196
|
-
|
|
277
|
+
mdb_txn_abort(transaction->txn);
|
|
278
|
+
*/
|
|
197
279
|
|
|
198
280
|
// eliminate child transactions
|
|
199
|
-
if (transaction->child) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
//
|
|
234
|
-
|
|
323
|
+
// original helpers
|
|
324
|
+
|
|
235
325
|
static VALUE call_with_transaction_helper(VALUE arg) {
|
|
236
|
-
|
|
326
|
+
HelperArgs* a = (HelperArgs*)arg;
|
|
327
|
+
return rb_funcall_passing_block(a->self, rb_intern(a->name), a->argc, a->argv);
|
|
237
328
|
}
|
|
238
|
-
|
|
239
|
-
static VALUE
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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),
|
|
315
|
-
|
|
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
|
-
|
|
318
|
-
TxnArgs txn_args;
|
|
518
|
+
VALUE thread = rb_thread_current();
|
|
319
519
|
|
|
320
|
-
|
|
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 (
|
|
326
|
-
|
|
524
|
+
if (REXISTS(vparent))
|
|
525
|
+
TypedData_Get_Struct(vparent, Transaction, &lmdb_transaction_type, tparent);
|
|
327
526
|
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
342
|
-
// We are reusing the parent transaction.
|
|
530
|
+
// ACQUIRE THE TRANSACTION FROM LMDB
|
|
343
531
|
|
|
344
|
-
|
|
345
|
-
|
|
532
|
+
MDB_txn* txn = NULL;
|
|
533
|
+
TxnArgs txn_args;
|
|
346
534
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
385
|
-
|
|
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
|
-
//
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
418
|
-
|
|
619
|
+
Transaction* transaction;
|
|
620
|
+
VALUE vtxn = TypedData_Make_Struct(cTransaction, Transaction,
|
|
621
|
+
&lmdb_transaction_type, transaction);
|
|
419
622
|
|
|
420
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
static void environment_free(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
803
|
+
return ret;
|
|
495
804
|
}
|
|
496
805
|
|
|
497
806
|
static VALUE flags2hash(int flags) {
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
846
|
+
MDB_envinfo info;
|
|
538
847
|
|
|
539
|
-
|
|
540
|
-
|
|
848
|
+
ENVIRONMENT(self, &lmdb_environment_type, environment);
|
|
849
|
+
check(mdb_env_info(environment->env, &info));
|
|
541
850
|
|
|
542
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
898
|
+
ENVIRONMENT(self, &lmdb_environment_type, environment);
|
|
590
899
|
|
|
591
|
-
|
|
592
|
-
|
|
900
|
+
VALUE force;
|
|
901
|
+
rb_scan_args(argc, argv, "01", &force);
|
|
593
902
|
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
908
|
+
ID id = rb_to_id(key);
|
|
600
909
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
923
|
+
else {
|
|
924
|
+
VALUE s = rb_inspect(key);
|
|
925
|
+
rb_raise(cError, "Invalid option %s", StringValueCStr(s));
|
|
926
|
+
}
|
|
618
927
|
|
|
619
|
-
|
|
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
|
-
|
|
966
|
+
VALUE path, option_hash;
|
|
658
967
|
|
|
659
968
|
#ifdef RB_SCAN_ARGS_KEYWORDS
|
|
660
|
-
|
|
661
|
-
|
|
969
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
|
970
|
+
argc, argv, "1:", &path, &option_hash);
|
|
662
971
|
#else
|
|
663
|
-
|
|
972
|
+
rb_scan_args(argc, argv, "1:", &path, &option_hash);
|
|
664
973
|
#endif
|
|
665
974
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
check(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1032
|
+
unsigned int flags;
|
|
1033
|
+
ENVIRONMENT(self, &lmdb_environment_type, environment);
|
|
1034
|
+
check(mdb_env_get_flags(environment->env, &flags));
|
|
724
1035
|
|
|
725
|
-
|
|
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
|
-
|
|
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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
1063
|
+
ENVIRONMENT(self, &lmdb_environment_type, environment);
|
|
753
1064
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1065
|
+
int i;
|
|
1066
|
+
for (i = 0; i < argc; ++i) {
|
|
1067
|
+
ID id = rb_to_id(argv[i]);
|
|
757
1068
|
|
|
758
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
780
|
-
|
|
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
|
-
|
|
795
|
-
|
|
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
|
-
|
|
809
|
-
|
|
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
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
842
|
-
|
|
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
|
-
|
|
1182
|
+
MDB_txn* txn = active_txn(self);
|
|
847
1183
|
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
1222
|
+
rb_need_block();
|
|
887
1223
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1224
|
+
VALUE readonly;
|
|
1225
|
+
rb_scan_args(argc, argv, "01", &readonly);
|
|
1226
|
+
unsigned int flags = RTEST(readonly) ? MDB_RDONLY : 0;
|
|
891
1227
|
|
|
892
|
-
|
|
1228
|
+
return with_transaction(self, rb_yield, Qnil, flags);
|
|
893
1229
|
}
|
|
894
1230
|
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
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
|
-
|
|
948
|
-
|
|
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
|
-
|
|
957
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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
|
-
|
|
966
|
-
|
|
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
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
-
|
|
1421
|
+
mdb_cursor_close(cursor);
|
|
1017
1422
|
|
|
1018
|
-
|
|
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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
-
|
|
1592
|
+
VALUE vkey, vval, option_hash = Qnil;
|
|
1185
1593
|
#ifdef RB_SCAN_ARGS_KEYWORDS
|
|
1186
|
-
|
|
1187
|
-
|
|
1594
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
|
1595
|
+
argc, argv, "20:", &vkey, &vval, &option_hash);
|
|
1188
1596
|
#else
|
|
1189
|
-
|
|
1597
|
+
rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
|
|
1190
1598
|
#endif
|
|
1191
1599
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
1198
|
-
|
|
1605
|
+
vkey = StringValue(vkey);
|
|
1606
|
+
vval = StringValue(vval);
|
|
1199
1607
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
-
|
|
1207
|
-
|
|
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
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
-
|
|
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(
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
1667
|
+
xfree(cursor);
|
|
1259
1668
|
}
|
|
1260
1669
|
|
|
1261
1670
|
static void cursor_check(Cursor* cursor) {
|
|
1262
|
-
|
|
1263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1345
|
-
|
|
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
|
-
|
|
1357
|
-
|
|
1803
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1804
|
+
MDB_val key, value;
|
|
1358
1805
|
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
-
|
|
1372
|
-
|
|
1818
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1819
|
+
MDB_val key, value;
|
|
1373
1820
|
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
1387
|
-
|
|
1833
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1834
|
+
MDB_val key, value;
|
|
1388
1835
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
-
|
|
1857
|
+
rb_scan_args(argc, argv, "01", &nodup);
|
|
1411
1858
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1859
|
+
if (RTEST(nodup))
|
|
1860
|
+
op = MDB_NEXT_NODUP;
|
|
1414
1861
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
-
|
|
1434
|
-
|
|
1880
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1881
|
+
MDB_val key, value, ub_key;
|
|
1435
1882
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
-
|
|
1442
|
-
|
|
1888
|
+
ub_key.mv_size = RSTRING_LEN(upper_bound_key);
|
|
1889
|
+
ub_key.mv_data = StringValuePtr(upper_bound_key);
|
|
1443
1890
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1891
|
+
MDB_txn* txn = mdb_cursor_txn(cursor->cur);
|
|
1892
|
+
MDB_dbi dbi = mdb_cursor_dbi(cursor->cur);
|
|
1446
1893
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
-
|
|
1917
|
+
rb_scan_args(argc, argv, "11", &vkey, &vval);
|
|
1471
1918
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
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
|
-
|
|
1480
|
-
|
|
1926
|
+
if (TYPE(vkey) != T_STRING)
|
|
1927
|
+
rb_raise(rb_eArgError, "key must be a string");
|
|
1481
1928
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1929
|
+
key.mv_size = RSTRING_LEN(vkey);
|
|
1930
|
+
key.mv_data = StringValuePtr(vkey);
|
|
1484
1931
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1936
|
+
op = MDB_GET_BOTH;
|
|
1937
|
+
value.mv_size = RSTRING_LEN(vval);
|
|
1938
|
+
value.mv_data = StringValuePtr(vval);
|
|
1939
|
+
}
|
|
1493
1940
|
|
|
1494
|
-
|
|
1941
|
+
ret = mdb_cursor_get(cursor->cur, &key, &value, op);
|
|
1495
1942
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1943
|
+
if (!NIL_P(vval) && ret == MDB_NOTFOUND)
|
|
1944
|
+
return Qnil;
|
|
1498
1945
|
|
|
1499
|
-
|
|
1946
|
+
check(ret);
|
|
1500
1947
|
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1959
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1960
|
+
MDB_val key, value;
|
|
1961
|
+
int ret;
|
|
1515
1962
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1963
|
+
key.mv_size = RSTRING_LEN(vkey);
|
|
1964
|
+
key.mv_data = StringValuePtr(vkey);
|
|
1518
1965
|
|
|
1519
|
-
|
|
1966
|
+
ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
|
|
1520
1967
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1968
|
+
/* not sure why we were letting this throw an exception */
|
|
1969
|
+
if (ret == MDB_NOTFOUND) return Qnil;
|
|
1523
1970
|
|
|
1524
|
-
|
|
1971
|
+
check(ret);
|
|
1525
1972
|
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
|
|
1984
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1538
1985
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
-
|
|
2032
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1586
2033
|
|
|
1587
|
-
|
|
2034
|
+
VALUE vkey, vval, option_hash;
|
|
1588
2035
|
#ifdef RB_SCAN_ARGS_KEYWORDS
|
|
1589
|
-
|
|
1590
|
-
|
|
2036
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
|
2037
|
+
argc, argv, "2:", &vkey, &vval, &option_hash);
|
|
1591
2038
|
#else
|
|
1592
|
-
|
|
2039
|
+
rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
|
|
1593
2040
|
#endif
|
|
1594
2041
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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
|
-
|
|
1601
|
-
|
|
2047
|
+
vkey = StringValue(vkey);
|
|
2048
|
+
vval = StringValue(vval);
|
|
1602
2049
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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
|
-
|
|
1610
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2075
|
+
CURSOR(self, &lmdb_cursor_type, cursor);
|
|
1629
2076
|
|
|
1630
|
-
|
|
2077
|
+
VALUE option_hash;
|
|
1631
2078
|
#ifdef RB_SCAN_ARGS_KEYWORDS
|
|
1632
|
-
|
|
1633
|
-
|
|
2079
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
|
2080
|
+
argc, argv, ":", &option_hash);
|
|
1634
2081
|
#else
|
|
1635
|
-
|
|
2082
|
+
rb_scan_args(argc, argv, ":", &option_hash);
|
|
1636
2083
|
#endif
|
|
1637
2084
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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
|
-
|
|
1644
|
-
|
|
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
|
-
|
|
1653
|
-
|
|
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
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
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
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
2131
|
+
VERSION_CONST(MAJOR)
|
|
2132
|
+
VERSION_CONST(MINOR)
|
|
2133
|
+
VERSION_CONST(PATCH)
|
|
1687
2134
|
#undef VERSION_CONST
|
|
1688
2135
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
#define ERROR(name)
|
|
1696
|
-
|
|
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
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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
|
}
|