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