lmdb 0.4.7 → 0.6
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 +5 -5
- data/.gitignore +6 -1
- data/.travis.yml +6 -5
- data/CHANGES +42 -0
- data/CONTRIBUTORS +1 -0
- data/README.md +1 -1
- data/Rakefile +9 -0
- data/behaviour.org +35 -0
- data/ext/lmdb_ext/env_flags.h +3 -0
- data/ext/lmdb_ext/errors.h +6 -0
- data/ext/lmdb_ext/extconf.rb +1 -0
- data/ext/lmdb_ext/lmdb_ext.c +363 -113
- data/ext/lmdb_ext/lmdb_ext.h +25 -5
- data/lib/lmdb/database.rb +118 -4
- data/lib/lmdb/version.rb +1 -1
- data/lmdb.gemspec +4 -3
- data/spec/helper.rb +3 -0
- data/spec/lmdb_spec.rb +109 -24
- metadata +28 -14
data/ext/lmdb_ext/lmdb_ext.c
CHANGED
@@ -26,6 +26,7 @@ static void check(int code) {
|
|
26
26
|
|
27
27
|
const char* err = mdb_strerror(code);
|
28
28
|
const char* sep = strchr(err, ':');
|
29
|
+
// increment the offset by two in case there is a colon (plus space)
|
29
30
|
if (sep)
|
30
31
|
err = sep + 2;
|
31
32
|
|
@@ -37,15 +38,19 @@ static void check(int code) {
|
|
37
38
|
}
|
38
39
|
|
39
40
|
static void transaction_free(Transaction* transaction) {
|
40
|
-
if (transaction->txn) {
|
41
|
-
|
42
|
-
|
41
|
+
if (transaction->txn && !NIL_P(transaction->txn)) {
|
42
|
+
//int id = (int)mdb_txn_id(transaction->txn);
|
43
|
+
//rb_warn(sprintf("Memory leak: Garbage collecting active transaction %d", id));
|
44
|
+
rb_warn("Memory leak: Garbage collecting active transaction");
|
45
|
+
// transaction_abort(transaction);
|
46
|
+
// mdb_txn_abort(transaction->txn);
|
43
47
|
}
|
44
48
|
free(transaction);
|
45
49
|
}
|
46
50
|
|
47
51
|
static void transaction_mark(Transaction* transaction) {
|
48
52
|
rb_gc_mark(transaction->parent);
|
53
|
+
rb_gc_mark(transaction->child);
|
49
54
|
rb_gc_mark(transaction->env);
|
50
55
|
rb_gc_mark(transaction->cursors);
|
51
56
|
}
|
@@ -108,7 +113,7 @@ static VALUE transaction_abort(VALUE self) {
|
|
108
113
|
}
|
109
114
|
|
110
115
|
/**
|
111
|
-
* @overload
|
116
|
+
* @overload env
|
112
117
|
* @return [Environment] the environment in which this transaction is running.
|
113
118
|
* @example
|
114
119
|
* env.transaction do |t|
|
@@ -121,49 +126,85 @@ static VALUE transaction_env(VALUE self) {
|
|
121
126
|
return transaction->env;
|
122
127
|
}
|
123
128
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
VALUE p = environment_active_txn(transaction->env);
|
135
|
-
while (!NIL_P(p) && p != self) {
|
136
|
-
TRANSACTION(p, txn);
|
137
|
-
p = txn->parent;
|
138
|
-
}
|
139
|
-
if (p != self)
|
140
|
-
rb_raise(cError, "Transaction is not active");
|
141
|
-
|
142
|
-
int ret = 0;
|
143
|
-
if (commit)
|
144
|
-
ret = mdb_txn_commit(transaction->txn);
|
145
|
-
else
|
146
|
-
mdb_txn_abort(transaction->txn);
|
129
|
+
/**
|
130
|
+
* @overload readonly?
|
131
|
+
* @note This predicate is considered *unstable*; do not get used to it.
|
132
|
+
* @return [false,true] whether the transaction is read-only.
|
133
|
+
*/
|
134
|
+
static VALUE transaction_is_readonly(VALUE self) {
|
135
|
+
TRANSACTION(self, transaction);
|
136
|
+
//MDB_txn* txn = transaction->txn;
|
137
|
+
return (transaction->flags & MDB_RDONLY) ? Qtrue : Qfalse;
|
138
|
+
}
|
147
139
|
|
148
|
-
long i;
|
149
|
-
for (i=0; i<RARRAY_LEN(transaction->cursors); i++) {
|
150
|
-
VALUE cursor = RARRAY_AREF(transaction->cursors, i);
|
151
|
-
cursor_close(cursor);
|
152
|
-
}
|
153
|
-
rb_ary_clear(transaction->cursors);
|
154
140
|
|
155
|
-
|
156
|
-
|
141
|
+
static void transaction_finish(VALUE self, int commit) {
|
142
|
+
TRANSACTION(self, transaction);
|
143
|
+
|
144
|
+
if (!transaction->txn)
|
145
|
+
rb_raise(cError, "Transaction is already terminated");
|
146
|
+
|
147
|
+
if (transaction->thread != rb_thread_current())
|
148
|
+
rb_raise(cError, "The thread closing the transaction "
|
149
|
+
"is not the one that opened it");
|
150
|
+
|
151
|
+
// ensure the transaction being closed is the active one
|
152
|
+
VALUE p = environment_active_txn(transaction->env);
|
153
|
+
while (!NIL_P(p) && p != self) {
|
154
|
+
TRANSACTION(p, txn);
|
155
|
+
p = txn->parent;
|
156
|
+
}
|
157
|
+
// bail out if the transaction `self` is not the active one
|
158
|
+
if (p != self)
|
159
|
+
rb_raise(cError, "Transaction is not active");
|
160
|
+
|
161
|
+
// now eliminate the cursors
|
162
|
+
long i;
|
163
|
+
for (i=0; i<RARRAY_LEN(transaction->cursors); i++) {
|
164
|
+
VALUE cursor = RARRAY_AREF(transaction->cursors, i);
|
165
|
+
cursor_close(cursor);
|
166
|
+
}
|
167
|
+
rb_ary_clear(transaction->cursors);
|
168
|
+
|
169
|
+
// now actually finish the internal transaction
|
170
|
+
int ret = 0;
|
171
|
+
if (commit)
|
172
|
+
ret = mdb_txn_commit(transaction->txn);
|
173
|
+
else
|
174
|
+
mdb_txn_abort(transaction->txn);
|
175
|
+
|
176
|
+
// eliminate child transactions
|
177
|
+
if (transaction->child) {
|
178
|
+
p = self; // again this is a VALUE
|
179
|
+
Transaction* txn = transaction; // and this is the struct
|
180
|
+
|
181
|
+
// descend into deepest child transaction
|
182
|
+
do {
|
183
|
+
p = txn->child;
|
184
|
+
// this is TRANSACTION minus the declaration
|
185
|
+
Data_Get_Struct(txn->child, Transaction, txn);
|
186
|
+
} while (txn->child);
|
187
|
+
|
188
|
+
// now we ascend back up
|
157
189
|
while (p != self) {
|
158
|
-
|
159
|
-
|
160
|
-
|
190
|
+
TRANSACTION(p, txn);
|
191
|
+
txn->txn = 0;
|
192
|
+
p = txn->parent;
|
161
193
|
}
|
162
|
-
|
194
|
+
}
|
195
|
+
transaction->txn = 0;
|
163
196
|
|
164
|
-
|
197
|
+
// no more active read-write transaction; unset the registry
|
198
|
+
if (!(transaction->flags & MDB_RDONLY) && !transaction->parent) {
|
199
|
+
ENVIRONMENT(transaction->env, env);
|
200
|
+
env->rw_txn_thread = NULL;
|
201
|
+
}
|
165
202
|
|
166
|
-
|
203
|
+
// now set the active transaction to the parent, if there is one
|
204
|
+
environment_set_active_txn(transaction->env, transaction->thread,
|
205
|
+
transaction->parent);
|
206
|
+
|
207
|
+
check(ret);
|
167
208
|
}
|
168
209
|
|
169
210
|
// Ruby 1.8.7 compatibility
|
@@ -204,63 +245,143 @@ static void stop_txn_begin(void *arg)
|
|
204
245
|
txn_args->stop = 1;
|
205
246
|
}
|
206
247
|
|
248
|
+
/**
|
249
|
+
* This is the code that opens transactions. Read-write transactions
|
250
|
+
* have to be called outside the GVL because they will block without
|
251
|
+
*
|
252
|
+
*
|
253
|
+
* Here is the basic problem with LMDB transactions:
|
254
|
+
*
|
255
|
+
* - There can only be one read-write transaction per LMDB
|
256
|
+
* ENVIRONMENT, not process, not thread.
|
257
|
+
*
|
258
|
+
* - Read-write transactions can nest.
|
259
|
+
*
|
260
|
+
* - There can only be one active transaction per thread.
|
261
|
+
*
|
262
|
+
* - Every thread can have an active read-ONLY transaction, but only one.
|
263
|
+
*
|
264
|
+
* - This is because read-only transactions canNOT nest (it is
|
265
|
+
* meaningless to have a nested read-only transaction)
|
266
|
+
*
|
267
|
+
* - Furthermore it is an error to open a read-only transaction under
|
268
|
+
* a read-write transaction.
|
269
|
+
*
|
270
|
+
* - Same goes for opening a read-write transaction in the same thread
|
271
|
+
* as an active read-only transaction.
|
272
|
+
*
|
273
|
+
* Nevertheless, the downstream Ruby user may not be able to
|
274
|
+
* completely control calls to env.transaction (e.g. if they are
|
275
|
+
* wrapping a transaction around a proc or lambda that happens to
|
276
|
+
* contain its own transaction), plus every call in LMDB needs to be
|
277
|
+
* implicitly wrapped in a transaction anyway.
|
278
|
+
*
|
279
|
+
* What this means is that we will, first, have to keep explicit track
|
280
|
+
* of the read-write transaction, if one exists. Second, we will have
|
281
|
+
* to simulate nesting for read-only transactions, so the behaviour in
|
282
|
+
* Ruby is the same as a read-write transaction.
|
283
|
+
*
|
284
|
+
*/
|
285
|
+
|
207
286
|
static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flags) {
|
208
|
-
|
287
|
+
ENVIRONMENT(venv, environment);
|
288
|
+
|
289
|
+
MDB_txn* txn;
|
290
|
+
TxnArgs txn_args;
|
291
|
+
|
292
|
+
VALUE thread = rb_thread_current();
|
293
|
+
VALUE vparent = environment_active_txn(venv);
|
294
|
+
|
295
|
+
Transaction* tparent = NULL;
|
296
|
+
if (vparent && !NIL_P(vparent))
|
297
|
+
Data_Get_Struct(vparent, Transaction, tparent);
|
209
298
|
|
210
|
-
|
211
|
-
TxnArgs txn_args;
|
299
|
+
// rb_warn("fart lol");
|
212
300
|
|
213
|
-
|
214
|
-
|
301
|
+
// XXX note this is a cursed goto loop that could almost certainly
|
302
|
+
// be rewritten as a do-while
|
303
|
+
retry:
|
304
|
+
txn = NULL;
|
215
305
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
306
|
+
txn_args.env = environment->env;
|
307
|
+
txn_args.parent = active_txn(venv);
|
308
|
+
txn_args.flags = flags;
|
309
|
+
txn_args.htxn = &txn;
|
310
|
+
txn_args.result = 0;
|
311
|
+
txn_args.stop = 0;
|
222
312
|
|
223
|
-
|
224
|
-
|
313
|
+
if (flags & MDB_RDONLY) {
|
314
|
+
if (tparent && tparent->flags & MDB_RDONLY)
|
315
|
+
// this is a no-op: put the same actual transaction in a
|
316
|
+
// different wrapper struct
|
317
|
+
txn = txn_args.parent;
|
318
|
+
else
|
319
|
+
// this will return an error if the parent transaction is
|
320
|
+
// read-write, so we don't need to handle the case explicitly
|
321
|
+
call_txn_begin(&txn_args);
|
322
|
+
}
|
323
|
+
else {
|
324
|
+
if (tparent) {
|
325
|
+
// first we have to determine if we're on the same thread
|
326
|
+
// as the parent, which in turn must be the same as the
|
327
|
+
// environment's registry for which thread has the
|
328
|
+
// read-write transaction
|
329
|
+
if (thread != tparent->thread ||
|
330
|
+
thread != environment->rw_txn_thread)
|
331
|
+
rb_raise(cError,
|
332
|
+
"Attempt to nest transaction on a different thread");
|
225
333
|
}
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
334
|
+
|
335
|
+
CALL_WITHOUT_GVL(call_txn_begin, &txn_args, stop_txn_begin, &txn_args);
|
336
|
+
|
337
|
+
if (txn_args.stop || !txn) {
|
338
|
+
// !txn is when rb_thread_call_without_gvl2
|
339
|
+
// returns before calling txn_begin
|
340
|
+
if (txn) {
|
341
|
+
mdb_txn_abort(txn);
|
342
|
+
txn_args.result = 0;
|
343
|
+
}
|
344
|
+
|
345
|
+
//rb_warn("got here lol");
|
346
|
+
rb_thread_check_ints();
|
347
|
+
goto retry; // in what cases do we get here?
|
240
348
|
}
|
241
349
|
|
242
|
-
|
350
|
+
// set the thread
|
351
|
+
environment->rw_txn_thread = thread;
|
352
|
+
}
|
243
353
|
|
244
|
-
|
245
|
-
|
246
|
-
transaction->parent = environment_active_txn(venv);
|
247
|
-
transaction->env = venv;
|
248
|
-
transaction->txn = txn;
|
249
|
-
transaction->thread = rb_thread_current();
|
250
|
-
transaction->cursors = rb_ary_new();
|
251
|
-
environment_set_active_txn(venv, transaction->thread, vtxn);
|
354
|
+
// this will raise unless result is zero
|
355
|
+
check(txn_args.result);
|
252
356
|
|
253
|
-
|
254
|
-
|
357
|
+
Transaction* transaction;
|
358
|
+
VALUE vtxn = Data_Make_Struct(cTransaction, Transaction, transaction_mark,
|
359
|
+
transaction_free, transaction);
|
360
|
+
transaction->parent = vparent;
|
361
|
+
transaction->env = venv;
|
362
|
+
transaction->txn = txn;
|
363
|
+
transaction->flags = flags;
|
364
|
+
transaction->thread = rb_thread_current();
|
365
|
+
transaction->cursors = rb_ary_new();
|
255
366
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
367
|
+
// set the parent's child to self
|
368
|
+
if (tparent) tparent->child = vtxn;
|
369
|
+
|
370
|
+
environment_set_active_txn(venv, transaction->thread, vtxn);
|
371
|
+
|
372
|
+
// now we run the function in the transaction
|
373
|
+
int exception;
|
374
|
+
VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
|
375
|
+
|
376
|
+
if (exception) {
|
377
|
+
// rb_warn("lol got exception");
|
261
378
|
if (vtxn == environment_active_txn(venv))
|
262
|
-
|
263
|
-
|
379
|
+
transaction_abort(vtxn);
|
380
|
+
rb_jump_tag(exception);
|
381
|
+
}
|
382
|
+
if (vtxn == environment_active_txn(venv))
|
383
|
+
transaction_commit(vtxn);
|
384
|
+
return ret;
|
264
385
|
}
|
265
386
|
|
266
387
|
static void environment_check(Environment* environment) {
|
@@ -319,6 +440,16 @@ static VALUE stat2hash(const MDB_stat* stat) {
|
|
319
440
|
return ret;
|
320
441
|
}
|
321
442
|
|
443
|
+
static VALUE flags2hash(int flags) {
|
444
|
+
VALUE ret = rb_hash_new();
|
445
|
+
|
446
|
+
#define FLAG(const, name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), (flags & MDB_##const) == 0 ? Qfalse : Qtrue);
|
447
|
+
#include "dbi_flags.h"
|
448
|
+
#undef FLAG
|
449
|
+
|
450
|
+
return ret;
|
451
|
+
}
|
452
|
+
|
322
453
|
/**
|
323
454
|
* @overload stat
|
324
455
|
* Return useful statistics about an environment.
|
@@ -356,7 +487,7 @@ static VALUE environment_info(VALUE self) {
|
|
356
487
|
|
357
488
|
VALUE ret = rb_hash_new();
|
358
489
|
|
359
|
-
#define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)),
|
490
|
+
#define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), SIZET2NUM((size_t)info.me_##name));
|
360
491
|
INFO_SET(mapaddr);
|
361
492
|
INFO_SET(mapsize);
|
362
493
|
INFO_SET(last_pgno);
|
@@ -470,7 +601,13 @@ static int environment_options(VALUE key, VALUE value, EnvironmentOptions* optio
|
|
470
601
|
*/
|
471
602
|
static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
|
472
603
|
VALUE path, option_hash;
|
604
|
+
|
605
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
606
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
607
|
+
argc, argv, "1:", &path, &option_hash);
|
608
|
+
#else
|
473
609
|
rb_scan_args(argc, argv, "1:", &path, &option_hash);
|
610
|
+
#endif
|
474
611
|
|
475
612
|
EnvironmentOptions options = {
|
476
613
|
.flags = MDB_NOTLS,
|
@@ -641,7 +778,7 @@ static MDB_txn* active_txn(VALUE self) {
|
|
641
778
|
return 0;
|
642
779
|
TRANSACTION(vtxn, transaction);
|
643
780
|
if (!transaction->txn)
|
644
|
-
rb_raise(cError, "Transaction is terminated");
|
781
|
+
rb_raise(cError, "Transaction is already terminated");
|
645
782
|
if (transaction->thread != rb_thread_current())
|
646
783
|
rb_raise(cError, "Wrong thread");
|
647
784
|
return transaction->txn;
|
@@ -745,7 +882,13 @@ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
|
|
745
882
|
return call_with_transaction(self, self, "database", argc, argv, 0);
|
746
883
|
|
747
884
|
VALUE name, option_hash;
|
885
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
886
|
+
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS,
|
887
|
+
argc, argv, "01:", &name, &option_hash);
|
888
|
+
#else
|
748
889
|
rb_scan_args(argc, argv, "01:", &name, &option_hash);
|
890
|
+
#endif
|
891
|
+
|
749
892
|
|
750
893
|
int flags = 0;
|
751
894
|
if (!NIL_P(option_hash))
|
@@ -776,13 +919,63 @@ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
|
|
776
919
|
static VALUE database_stat(VALUE self) {
|
777
920
|
DATABASE(self, database);
|
778
921
|
if (!active_txn(database->env))
|
779
|
-
return call_with_transaction(database->env,
|
922
|
+
return call_with_transaction(database->env,
|
923
|
+
self, "stat", 0, 0, MDB_RDONLY);
|
780
924
|
|
781
925
|
MDB_stat stat;
|
782
926
|
check(mdb_stat(need_txn(database->env), database->dbi, &stat));
|
783
927
|
return stat2hash(&stat);
|
784
928
|
}
|
785
929
|
|
930
|
+
/**
|
931
|
+
* @overload flags
|
932
|
+
* Return the flags used to open the database.
|
933
|
+
* @return [Hash] The flags.
|
934
|
+
*/
|
935
|
+
static VALUE database_get_flags(VALUE self) {
|
936
|
+
DATABASE(self, database);
|
937
|
+
if (!active_txn(database->env))
|
938
|
+
return call_with_transaction(database->env,
|
939
|
+
self, "flags", 0, 0, MDB_RDONLY);
|
940
|
+
unsigned int flags;
|
941
|
+
check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
|
942
|
+
return flags2hash(flags);
|
943
|
+
}
|
944
|
+
|
945
|
+
/* XXX these two could probably also be macro'd, or maybe not i dunno */
|
946
|
+
|
947
|
+
/**
|
948
|
+
* @overload dupsort?
|
949
|
+
* Returns whether the database is in +:dupsort+ mode.
|
950
|
+
* @return [true, false]
|
951
|
+
*/
|
952
|
+
static VALUE database_is_dupsort(VALUE self) {
|
953
|
+
DATABASE(self, database);
|
954
|
+
if (!active_txn(database->env))
|
955
|
+
return call_with_transaction(database->env, self,
|
956
|
+
"dupsort?", 0, 0, MDB_RDONLY);
|
957
|
+
unsigned int flags;
|
958
|
+
check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
|
959
|
+
|
960
|
+
return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
|
961
|
+
}
|
962
|
+
|
963
|
+
/**
|
964
|
+
* @overload dupfixed?
|
965
|
+
* Returns whether the database is in +:dupfixed+ mode.
|
966
|
+
* @return [true, false]
|
967
|
+
*/
|
968
|
+
static VALUE database_is_dupfixed(VALUE self) {
|
969
|
+
DATABASE(self, database);
|
970
|
+
if (!active_txn(database->env))
|
971
|
+
return call_with_transaction(database->env, self,
|
972
|
+
"dupfixed?", 0, 0, MDB_RDONLY);
|
973
|
+
unsigned int flags;
|
974
|
+
check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
|
975
|
+
|
976
|
+
return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
|
977
|
+
}
|
978
|
+
|
786
979
|
/**
|
787
980
|
* @overload drop
|
788
981
|
* Remove a database from the environment.
|
@@ -875,8 +1068,13 @@ static VALUE database_put(int argc, VALUE *argv, VALUE self) {
|
|
875
1068
|
if (!active_txn(database->env))
|
876
1069
|
return call_with_transaction(database->env, self, "put", argc, argv, 0);
|
877
1070
|
|
878
|
-
VALUE vkey, vval, option_hash;
|
879
|
-
|
1071
|
+
VALUE vkey, vval, option_hash = Qnil;
|
1072
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
1073
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
1074
|
+
argc, argv, "20:", &vkey, &vval, &option_hash);
|
1075
|
+
#else
|
1076
|
+
rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
|
1077
|
+
#endif
|
880
1078
|
|
881
1079
|
int flags = 0;
|
882
1080
|
if (!NIL_P(option_hash))
|
@@ -927,7 +1125,7 @@ static VALUE database_delete(int argc, VALUE *argv, VALUE self) {
|
|
927
1125
|
if (NIL_P(vval)) {
|
928
1126
|
check(mdb_del(need_txn(database->env), database->dbi, &key, 0));
|
929
1127
|
} else {
|
930
|
-
|
1128
|
+
vval = StringValue(vval);
|
931
1129
|
MDB_val value;
|
932
1130
|
value.mv_size = RSTRING_LEN(vval);
|
933
1131
|
value.mv_data = RSTRING_PTR(vval);
|
@@ -1025,7 +1223,7 @@ static VALUE database_cursor(VALUE self) {
|
|
1025
1223
|
}
|
1026
1224
|
|
1027
1225
|
/**
|
1028
|
-
* @overload
|
1226
|
+
* @overload env
|
1029
1227
|
* @return [Environment] the environment to which this database belongs.
|
1030
1228
|
*/
|
1031
1229
|
static VALUE database_env(VALUE self) {
|
@@ -1082,21 +1280,30 @@ static VALUE cursor_prev(VALUE self) {
|
|
1082
1280
|
}
|
1083
1281
|
|
1084
1282
|
/**
|
1085
|
-
* @overload next
|
1283
|
+
* @overload next nodup = nil
|
1086
1284
|
* Position the cursor to the next record in the database, and
|
1087
1285
|
* return its value.
|
1286
|
+
* @param nodup [true, false] If true, skip over duplicate records.
|
1088
1287
|
* @return [Array,nil] The [key, value] pair for the next record, or
|
1089
1288
|
* nil if no next record.
|
1090
1289
|
*/
|
1091
|
-
static VALUE cursor_next(VALUE self) {
|
1290
|
+
static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
|
1092
1291
|
CURSOR(self, cursor);
|
1292
|
+
VALUE nodup;
|
1093
1293
|
MDB_val key, value;
|
1294
|
+
MDB_cursor_op op = MDB_NEXT;
|
1094
1295
|
|
1095
|
-
|
1296
|
+
rb_scan_args(argc, argv, "01", &nodup);
|
1297
|
+
|
1298
|
+
if (RTEST(nodup))
|
1299
|
+
op = MDB_NEXT_NODUP;
|
1300
|
+
|
1301
|
+
int ret = mdb_cursor_get(cursor->cur, &key, &value, op);
|
1096
1302
|
if (ret == MDB_NOTFOUND)
|
1097
1303
|
return Qnil;
|
1098
1304
|
check(ret);
|
1099
|
-
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1305
|
+
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1306
|
+
rb_str_new(value.mv_data, value.mv_size));
|
1100
1307
|
}
|
1101
1308
|
|
1102
1309
|
/**
|
@@ -1104,6 +1311,7 @@ static VALUE cursor_next(VALUE self) {
|
|
1104
1311
|
* Position the cursor to the next record in the database, and
|
1105
1312
|
* return its value if the record's key is less than or equal to
|
1106
1313
|
* the specified key, or nil otherwise.
|
1314
|
+
* @param key [#to_s] The key to serve as the upper bound
|
1107
1315
|
* @return [Array,nil] The [key, value] pair for the next record, or
|
1108
1316
|
* nil if no next record or the next record is out of the range.
|
1109
1317
|
*/
|
@@ -1119,7 +1327,7 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
|
|
1119
1327
|
ub_key.mv_size = RSTRING_LEN(upper_bound_key);
|
1120
1328
|
ub_key.mv_data = StringValuePtr(upper_bound_key);
|
1121
1329
|
|
1122
|
-
MDB_txn
|
1330
|
+
MDB_txn* txn = mdb_cursor_txn(cursor->cur);
|
1123
1331
|
MDB_dbi dbi = mdb_cursor_dbi(cursor->cur);
|
1124
1332
|
|
1125
1333
|
if (mdb_cmp(txn, dbi, &key, &ub_key) <= 0) {
|
@@ -1130,20 +1338,40 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
|
|
1130
1338
|
}
|
1131
1339
|
|
1132
1340
|
/**
|
1133
|
-
* @overload set(key)
|
1134
|
-
* Set the cursor to a specified key
|
1135
|
-
*
|
1136
|
-
* @
|
1341
|
+
* @overload set(key, value = nil)
|
1342
|
+
* Set the cursor to a specified key, optionally at the specified
|
1343
|
+
* value if the database was opened with +:dupsort+.
|
1344
|
+
* @param key [#to_s] The key to which the cursor should be positioned
|
1345
|
+
* @param value [nil, #to_s] The optional value (+:dupsort+ only)
|
1346
|
+
* @return [Array] The +[key, value]+ pair to which the cursor now points.
|
1137
1347
|
*/
|
1138
|
-
static VALUE cursor_set(VALUE
|
1139
|
-
|
1140
|
-
|
1348
|
+
static VALUE cursor_set(int argc, VALUE* argv, VALUE self) {
|
1349
|
+
CURSOR(self, cursor);
|
1350
|
+
VALUE vkey, vval;
|
1351
|
+
MDB_val key, value;
|
1352
|
+
MDB_cursor_op op = MDB_SET_KEY;
|
1353
|
+
int ret;
|
1141
1354
|
|
1142
|
-
|
1143
|
-
key.mv_data = StringValuePtr(vkey);
|
1355
|
+
rb_scan_args(argc, argv, "11", &vkey, &vval);
|
1144
1356
|
|
1145
|
-
|
1146
|
-
|
1357
|
+
key.mv_size = RSTRING_LEN(vkey);
|
1358
|
+
key.mv_data = StringValuePtr(vkey);
|
1359
|
+
|
1360
|
+
if (!NIL_P(vval)) {
|
1361
|
+
op = MDB_GET_BOTH;
|
1362
|
+
value.mv_size = RSTRING_LEN(vval);
|
1363
|
+
value.mv_data = StringValuePtr(vval);
|
1364
|
+
}
|
1365
|
+
|
1366
|
+
ret = mdb_cursor_get(cursor->cur, &key, &value, op);
|
1367
|
+
|
1368
|
+
if (!NIL_P(vval) && ret == MDB_NOTFOUND)
|
1369
|
+
return Qnil;
|
1370
|
+
|
1371
|
+
check(ret);
|
1372
|
+
|
1373
|
+
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1374
|
+
rb_str_new(value.mv_data, value.mv_size));
|
1147
1375
|
}
|
1148
1376
|
|
1149
1377
|
/**
|
@@ -1155,12 +1383,20 @@ static VALUE cursor_set(VALUE self, VALUE vkey) {
|
|
1155
1383
|
static VALUE cursor_set_range(VALUE self, VALUE vkey) {
|
1156
1384
|
CURSOR(self, cursor);
|
1157
1385
|
MDB_val key, value;
|
1386
|
+
int ret;
|
1158
1387
|
|
1159
1388
|
key.mv_size = RSTRING_LEN(vkey);
|
1160
1389
|
key.mv_data = StringValuePtr(vkey);
|
1161
1390
|
|
1162
|
-
|
1163
|
-
|
1391
|
+
ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
|
1392
|
+
|
1393
|
+
/* not sure why we were letting this throw an exception */
|
1394
|
+
if (ret == MDB_NOTFOUND) return Qnil;
|
1395
|
+
|
1396
|
+
check(ret);
|
1397
|
+
|
1398
|
+
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1399
|
+
rb_str_new(value.mv_data, value.mv_size));
|
1164
1400
|
}
|
1165
1401
|
|
1166
1402
|
/**
|
@@ -1221,7 +1457,12 @@ static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
|
|
1221
1457
|
CURSOR(self, cursor);
|
1222
1458
|
|
1223
1459
|
VALUE vkey, vval, option_hash;
|
1460
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
1461
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
1462
|
+
argc, argv, "2:", &vkey, &vval, &option_hash);
|
1463
|
+
#else
|
1224
1464
|
rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
|
1465
|
+
#endif
|
1225
1466
|
|
1226
1467
|
int flags = 0;
|
1227
1468
|
if (!NIL_P(option_hash))
|
@@ -1258,7 +1499,12 @@ static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
|
|
1258
1499
|
CURSOR(self, cursor);
|
1259
1500
|
|
1260
1501
|
VALUE option_hash;
|
1502
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
1503
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
1504
|
+
argc, argv, ":", &option_hash);
|
1505
|
+
#else
|
1261
1506
|
rb_scan_args(argc, argv, ":", &option_hash);
|
1507
|
+
#endif
|
1262
1508
|
|
1263
1509
|
int flags = 0;
|
1264
1510
|
if (!NIL_P(option_hash))
|
@@ -1388,6 +1634,9 @@ void Init_lmdb_ext() {
|
|
1388
1634
|
cDatabase = rb_define_class_under(mLMDB, "Database", rb_cObject);
|
1389
1635
|
rb_undef_method(rb_singleton_class(cDatabase), "new");
|
1390
1636
|
rb_define_method(cDatabase, "stat", database_stat, 0);
|
1637
|
+
rb_define_method(cDatabase, "flags", database_get_flags, 0);
|
1638
|
+
rb_define_method(cDatabase, "dupsort?", database_is_dupsort, 0);
|
1639
|
+
rb_define_method(cDatabase, "dupfixed?", database_is_dupfixed, 0);
|
1391
1640
|
rb_define_method(cDatabase, "drop", database_drop, 0);
|
1392
1641
|
rb_define_method(cDatabase, "clear", database_clear, 0);
|
1393
1642
|
rb_define_method(cDatabase, "get", database_get, 1);
|
@@ -1458,6 +1707,7 @@ void Init_lmdb_ext() {
|
|
1458
1707
|
rb_define_method(cTransaction, "commit", transaction_commit, 0);
|
1459
1708
|
rb_define_method(cTransaction, "abort", transaction_abort, 0);
|
1460
1709
|
rb_define_method(cTransaction, "env", transaction_env, 0);
|
1710
|
+
rb_define_method(cTransaction, "readonly?", transaction_is_readonly, 0);
|
1461
1711
|
|
1462
1712
|
/**
|
1463
1713
|
* Document-class: LMDB::Cursor
|
@@ -1491,10 +1741,10 @@ void Init_lmdb_ext() {
|
|
1491
1741
|
rb_define_method(cCursor, "get", cursor_get, 0);
|
1492
1742
|
rb_define_method(cCursor, "first", cursor_first, 0);
|
1493
1743
|
rb_define_method(cCursor, "last", cursor_last, 0);
|
1494
|
-
rb_define_method(cCursor, "next", cursor_next,
|
1744
|
+
rb_define_method(cCursor, "next", cursor_next, -1);
|
1495
1745
|
rb_define_method(cCursor, "next_range", cursor_next_range, 1);
|
1496
1746
|
rb_define_method(cCursor, "prev", cursor_prev, 0);
|
1497
|
-
rb_define_method(cCursor, "set", cursor_set, 1);
|
1747
|
+
rb_define_method(cCursor, "set", cursor_set, -1);
|
1498
1748
|
rb_define_method(cCursor, "set_range", cursor_set_range, 1);
|
1499
1749
|
rb_define_method(cCursor, "put", cursor_put, -1);
|
1500
1750
|
rb_define_method(cCursor, "count", cursor_count, 0);
|