isomorfeus-hamster 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1598 @@
1
+ #include "isomorfeus_hamster.h"
2
+ #include "extconf.h"
3
+
4
+ #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL2
5
+
6
+ // ruby 2
7
+ #include "ruby/thread.h"
8
+ #define CALL_WITHOUT_GVL(func, data1, ubf, data2) \
9
+ rb_thread_call_without_gvl2(func, data1, ubf, data2)
10
+
11
+ #else
12
+
13
+ // ruby 193
14
+ // Expose the API from internal.h:
15
+ VALUE rb_thread_call_without_gvl(
16
+ rb_blocking_function_t *func, void *data1,
17
+ rb_unblock_function_t *ubf, void *data2);
18
+ #define CALL_WITHOUT_GVL(func, data1, ubf, data2) \
19
+ rb_thread_call_without_gvl((rb_blocking_function_t *)func, data1, ubf, data2)
20
+
21
+ #endif
22
+
23
+ static void check(int code) {
24
+ if (!code)
25
+ return;
26
+
27
+ const char* err = mdb_strerror(code);
28
+ const char* sep = strchr(err, ':');
29
+ if (sep)
30
+ err = sep + 2;
31
+
32
+ #define OB_ERROR(name) if (code == MDB_##name) rb_raise(cError_##name, "%s", err);
33
+ #include "errors.h"
34
+ #undef OB_ERROR
35
+
36
+ rb_raise(cError, "%s", err); /* fallback */
37
+ }
38
+
39
+ static void transaction_free(Transaction* transaction) {
40
+ if (transaction->txn) {
41
+ rb_warn("Memory leak - Garbage collecting active transaction");
42
+ // mdb_txn_abort(transaction->txn);
43
+ }
44
+ free(transaction);
45
+ }
46
+
47
+ static void transaction_mark(Transaction* transaction) {
48
+ rb_gc_mark(transaction->parent);
49
+ rb_gc_mark(transaction->env);
50
+ rb_gc_mark(transaction->cursors);
51
+ }
52
+
53
+ /**
54
+ * Commit a transaction in process. Any subtransactions of this
55
+ * transaction will be committed as well.
56
+ *
57
+ * One does not normally need to call commit explicitly; a
58
+ * commit is performed automatically when the block supplied to
59
+ * {Environment#transaction} exits normally.
60
+ *
61
+ * @note After committing a transaction, no further database operations
62
+ * should be done in the block. Any cursors created in the context
63
+ * of the transaction will no longer be valid.
64
+ *
65
+ * @example Single transaction
66
+ * env.transaction do |txn|
67
+ * # ... modify the databases ...
68
+ * txn.commit
69
+ * end
70
+ *
71
+ * @example Child transactions
72
+ * env.transaction do |txn1|
73
+ * env.transaction.do |txn2|
74
+ * txn1.commit # txn1 and txn2 are both committed
75
+ * end
76
+ * end
77
+ */
78
+ static VALUE transaction_commit(VALUE self) {
79
+ transaction_finish(self, 1);
80
+ return Qnil;
81
+ }
82
+
83
+ /**
84
+ * Abort a transaction in process. Any subtransactions of this
85
+ * transaction will be aborted as well.
86
+ *
87
+ * @note After aborting a transaction, no further database operations
88
+ * should be done in the block. Any cursors created in the context
89
+ * of the transaction will no longer be valid.
90
+ *
91
+ * @example Single transaction
92
+ * env.transaction do |txn|
93
+ * # ... modify the databases ...
94
+ * txn.abort
95
+ * # modifications are rolled back
96
+ * end
97
+ *
98
+ * @example Child transactions
99
+ * env.transaction do |txn1|
100
+ * env.transaction.do |txn2|
101
+ * txn1.abort # txn1 and txn2 are both aborted
102
+ * end
103
+ * end
104
+ */
105
+ static VALUE transaction_abort(VALUE self) {
106
+ transaction_finish(self, 0);
107
+ return Qnil;
108
+ }
109
+
110
+ /**
111
+ * @overload env
112
+ * @return [Environment] the environment in which this transaction is running.
113
+ * @example
114
+ * env.transaction do |t|
115
+ * env == t.env
116
+ * # should be true
117
+ * end
118
+ */
119
+ static VALUE transaction_env(VALUE self) {
120
+ TRANSACTION(self, transaction);
121
+ return transaction->env;
122
+ }
123
+
124
+ static void transaction_finish(VALUE self, int commit) {
125
+ TRANSACTION(self, transaction);
126
+
127
+ if (!transaction->txn)
128
+ rb_raise(cError, "Transaction is terminated");
129
+
130
+ if (transaction->thread != rb_thread_current())
131
+ rb_raise(cError, "Wrong thread");
132
+
133
+ // Check nesting
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);
147
+
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
+
155
+ // Mark child transactions as closed
156
+ p = environment_active_txn(transaction->env);
157
+ while (p != self) {
158
+ TRANSACTION(p, txn);
159
+ txn->txn = 0;
160
+ p = txn->parent;
161
+ }
162
+ transaction->txn = 0;
163
+
164
+ environment_set_active_txn(transaction->env, transaction->thread, transaction->parent);
165
+
166
+ check(ret);
167
+ }
168
+
169
+ // Ruby 1.8.7 compatibility
170
+ #ifndef HAVE_RB_FUNCALL_PASSING_BLOCK
171
+ static VALUE call_with_transaction_helper(VALUE arg) {
172
+ #error "Not implemented"
173
+ }
174
+ #else
175
+ static VALUE call_with_transaction_helper(VALUE arg) {
176
+ HelperArgs* a = (HelperArgs*)arg;
177
+ return rb_funcall_passing_block(a->self, rb_intern(a->name), a->argc, a->argv);
178
+ }
179
+ #endif
180
+
181
+ static VALUE call_with_transaction(VALUE venv, VALUE self, const char* name, int argc, const VALUE* argv, int flags) {
182
+ HelperArgs arg = { self, name, argc, argv };
183
+ return with_transaction(venv, call_with_transaction_helper, (VALUE)&arg, flags);
184
+ }
185
+
186
+ static void *call_txn_begin(void *arg) {
187
+ TxnArgs *txn_args = arg;
188
+ txn_args->result = mdb_txn_begin(txn_args->env,
189
+ txn_args->parent, txn_args->flags, txn_args->htxn);
190
+ if (txn_args->result == MDB_MAP_RESIZED) {
191
+ check(mdb_env_set_mapsize(txn_args->env, 0));
192
+ txn_args->result = mdb_txn_begin(txn_args->env,
193
+ txn_args->parent, txn_args->flags, txn_args->htxn);
194
+ }
195
+ return (void *)NULL;
196
+ }
197
+
198
+ static void stop_txn_begin(void *arg)
199
+ {
200
+ TxnArgs *txn_args = arg;
201
+ // There's no way to stop waiting for mutex:
202
+ // http://www.cognitus.net/faq/pthread/pthreadSemiFAQ_6.html
203
+ // However, we can (and must) release the mutex as soon as we get it:
204
+ txn_args->stop = 1;
205
+ }
206
+
207
+ static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flags) {
208
+ ENVIRONMENT(venv, environment);
209
+
210
+ MDB_txn* txn;
211
+ TxnArgs txn_args;
212
+
213
+ retry:
214
+ txn = NULL;
215
+
216
+ txn_args.env = environment->env;
217
+ txn_args.parent = active_txn(venv);
218
+ txn_args.flags = flags;
219
+ txn_args.htxn = &txn;
220
+ txn_args.result = 0;
221
+ txn_args.stop = 0;
222
+
223
+ if (flags & MDB_RDONLY) {
224
+ call_txn_begin(&txn_args);
225
+ }
226
+ else {
227
+ CALL_WITHOUT_GVL(
228
+ call_txn_begin, &txn_args,
229
+ stop_txn_begin, &txn_args);
230
+
231
+ if (txn_args.stop || !txn) {
232
+ // !txn is when rb_thread_call_without_gvl2
233
+ // returns before calling txn_begin
234
+ if (txn) {
235
+ mdb_txn_abort(txn);
236
+ }
237
+ rb_thread_check_ints();
238
+ goto retry; // in what cases do we get here?
239
+ }
240
+ }
241
+
242
+ check(txn_args.result);
243
+
244
+ Transaction* transaction;
245
+ VALUE vtxn = Data_Make_Struct(cTransaction, Transaction, transaction_mark, transaction_free, transaction);
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);
252
+
253
+ int exception;
254
+ VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
255
+
256
+ if (exception) {
257
+ if (vtxn == environment_active_txn(venv))
258
+ transaction_abort(vtxn);
259
+ rb_jump_tag(exception);
260
+ }
261
+ if (vtxn == environment_active_txn(venv))
262
+ transaction_commit(vtxn);
263
+ return ret;
264
+ }
265
+
266
+ static void environment_check(Environment* environment) {
267
+ if (!environment->env)
268
+ rb_raise(cError, "Environment is closed");
269
+ }
270
+
271
+ static void environment_free(Environment *environment) {
272
+ if (environment->env) {
273
+ // rb_warn("Memory leak - Garbage collecting open environment");
274
+ if (!RHASH_EMPTY_P(environment->txn_thread_hash)) {
275
+ // If a transaction (or cursor) is open, its block is on the
276
+ // stack, so it will not be collected, so environment_free
277
+ // should not be called.
278
+ rb_warn("Bug: closing environment with open transactions.");
279
+ }
280
+ mdb_env_close(environment->env);
281
+ }
282
+ free(environment);
283
+ }
284
+
285
+
286
+ static void environment_mark(Environment* environment) {
287
+ rb_gc_mark(environment->thread_txn_hash);
288
+ rb_gc_mark(environment->txn_thread_hash);
289
+ }
290
+
291
+ /**
292
+ * @overload close
293
+ * Close an environment, completing all IOs and cleaning up database
294
+ * state if needed.
295
+ * @example
296
+ * env = LMDB.new('abc')
297
+ * # ...various operations on the environment...
298
+ * env.close
299
+ */
300
+ static VALUE environment_close(VALUE self) {
301
+ ENVIRONMENT(self, environment);
302
+ mdb_env_close(environment->env);
303
+ environment->env = 0;
304
+ return Qnil;
305
+ }
306
+
307
+ static VALUE stat2hash(const MDB_stat* stat) {
308
+ VALUE ret = rb_hash_new();
309
+
310
+ #define STAT_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), INT2NUM(stat->ms_##name))
311
+ STAT_SET(psize);
312
+ STAT_SET(depth);
313
+ STAT_SET(branch_pages);
314
+ STAT_SET(leaf_pages);
315
+ STAT_SET(overflow_pages);
316
+ STAT_SET(entries);
317
+ #undef STAT_SET
318
+
319
+ return ret;
320
+ }
321
+
322
+ static VALUE flags2hash(int flags) {
323
+ VALUE ret = rb_hash_new();
324
+
325
+ #define FLAG(const, name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), (flags & MDB_##const) == 0 ? Qfalse : Qtrue);
326
+ #include "dbi_flags.h"
327
+ #undef FLAG
328
+
329
+ return ret;
330
+ }
331
+
332
+ /**
333
+ * @overload stat
334
+ * Return useful statistics about an environment.
335
+ * @return [Hash] the statistics
336
+ * * +:psize+ Size of a database page
337
+ * * +:depth+ Depth (height) of the B-tree
338
+ * * +:branch_pages+ Number of internal (non-leaf) pages
339
+ * * +:leaf_pages+ Number of leaf pages
340
+ * * +:overflow_pages+ Number of overflow pages
341
+ * * +:entries+ Number of data items
342
+ */
343
+ static VALUE environment_stat(VALUE self) {
344
+ ENVIRONMENT(self, environment);
345
+ MDB_stat stat;
346
+ check(mdb_env_stat(environment->env, &stat));
347
+ return stat2hash(&stat);
348
+ }
349
+
350
+ /**
351
+ * @overload info
352
+ * Return useful information about an environment.
353
+ * @return [Hash]
354
+ * * +:mapaddr+ The memory address at which the database is mapped, if fixed
355
+ * * +:mapsize+ The size of the data memory map
356
+ * * +:last_pgno+ ID of the last used page
357
+ * * +:last_txnid+ ID of the last committed transaction
358
+ * * +:maxreaders+ Max reader slots in the environment
359
+ * * +:numreaders+ Max readers slots in the environment
360
+ */
361
+ static VALUE environment_info(VALUE self) {
362
+ MDB_envinfo info;
363
+
364
+ ENVIRONMENT(self, environment);
365
+ check(mdb_env_info(environment->env, &info));
366
+
367
+ VALUE ret = rb_hash_new();
368
+
369
+ #define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), SIZET2NUM((size_t)info.me_##name));
370
+ INFO_SET(mapaddr);
371
+ INFO_SET(mapsize);
372
+ INFO_SET(last_pgno);
373
+ INFO_SET(last_txnid);
374
+ INFO_SET(maxreaders);
375
+ INFO_SET(numreaders);
376
+ #undef INFO_SET
377
+
378
+ return ret;
379
+ }
380
+
381
+ /**
382
+ * @overload copy(path)
383
+ * Create a copy (snapshot) of an environment. The copy can be used
384
+ * as a backup. The copy internally uses a read-only transaction to
385
+ * ensure that the copied data is serialized with respect to database
386
+ * updates.
387
+ * @param [String] path The directory in which the copy will
388
+ * reside. This directory must already exist and be writable but
389
+ * must otherwise be empty.
390
+ * @return nil
391
+ * @raise [Error] when there is an error creating the copy.
392
+ */
393
+ static VALUE environment_copy(VALUE self, VALUE path) {
394
+ ENVIRONMENT(self, environment);
395
+ VALUE expanded_path = rb_file_expand_path(path, Qnil);
396
+ check(mdb_env_copy(environment->env, StringValueCStr(expanded_path)));
397
+ return Qnil;
398
+ }
399
+
400
+ /**
401
+ * @overload sync(force)
402
+ * Flush the data buffers to disk.
403
+ *
404
+ * Data is always written to disk when {Transaction#commit} is called, but
405
+ * the operating system may keep it buffered. MDB always flushes the
406
+ * OS buffers upon commit as well, unless the environment was opened
407
+ * with +:nosync+ or in part +:nometasync+.
408
+ * @param [Boolean] force If true, force a synchronous
409
+ * flush. Otherwise if the environment has the +:nosync+ flag set
410
+ * the flushes will be omitted, and with +:mapasync+ they will be
411
+ * asynchronous.
412
+ */
413
+ static VALUE environment_sync(int argc, VALUE *argv, VALUE self) {
414
+ ENVIRONMENT(self, environment);
415
+
416
+ VALUE force;
417
+ rb_scan_args(argc, argv, "01", &force);
418
+
419
+ check(mdb_env_sync(environment->env, RTEST(force)));
420
+ return Qnil;
421
+ }
422
+
423
+ static int environment_options(VALUE key, VALUE value, EnvironmentOptions* options) {
424
+ ID id = rb_to_id(key);
425
+
426
+ if (id == rb_intern("mode"))
427
+ options->mode = NUM2INT(value);
428
+ else if (id == rb_intern("maxreaders"))
429
+ options->maxreaders = NUM2INT(value);
430
+ else if (id == rb_intern("maxdbs"))
431
+ options->maxdbs = NUM2INT(value);
432
+ else if (id == rb_intern("mapsize"))
433
+ options->mapsize = NUM2SSIZET(value);
434
+
435
+ #define FLAG(const, name) else if (id == rb_intern(#name)) { if (RTEST(value)) { options->flags |= MDB_##const; } }
436
+ #include "env_flags.h"
437
+ #undef FLAG
438
+
439
+ else {
440
+ VALUE s = rb_inspect(key);
441
+ rb_raise(cError, "Invalid option %s", StringValueCStr(s));
442
+ }
443
+
444
+ return 0;
445
+ }
446
+
447
+ /**
448
+ * @overload new(path, opts)
449
+ * Open an LMDB database environment.
450
+ * The database environment is the root object for all operations on
451
+ * a collection of databases. It has to be opened first, before
452
+ * individual databases can be opened or created in the environment.
453
+ * The database should be closed when it is no longer needed.
454
+ *
455
+ * The options hash on this method includes all the flags listed in
456
+ * {Environment#flags} as well as the options documented here.
457
+ * @return [Environment]
458
+ * @param [String] path the path to the files containing the database
459
+ * @param [Hash] opts options for the database environment
460
+ * @option opts [Number] :mode The Posix permissions to set on created files.
461
+ * @option opts [Number] :maxreaders The maximum number of concurrent threads
462
+ * that can be executing transactions at once. Default is 126.
463
+ * @option opts [Number] :maxdbs The maximum number of named databases in the
464
+ * environment. Not needed if only one database is being used.
465
+ * @option opts [Number] :mapsize The size of the memory map to be allocated
466
+ * for this environment, in bytes. The memory map size is the
467
+ * maximum total size of the database. The size should be a
468
+ * multiple of the OS page size. The default size is about
469
+ * 10MiB.
470
+ * @yield [env] The block to be executed with the environment. The environment is closed afterwards.
471
+ * @yieldparam env [Environment] The environment
472
+ * @see #close
473
+ * @see Environment#flags
474
+ * @example Open environment and pass options
475
+ * env = LMDB.new "dbdir", :maxdbs => 30, :mapasync => true, :writemap => true
476
+ * @example Pass environment to block
477
+ * LMDB.new "dbdir" do |env|
478
+ * # ...
479
+ * end
480
+ */
481
+ static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
482
+ VALUE path, option_hash;
483
+ rb_scan_args(argc, argv, "1:", &path, &option_hash);
484
+
485
+ EnvironmentOptions options = {
486
+ .flags = MDB_NOTLS,
487
+ .maxreaders = -1,
488
+ .maxdbs = 128,
489
+ .mapsize = 0,
490
+ .mode = 0755,
491
+ };
492
+ if (!NIL_P(option_hash))
493
+ rb_hash_foreach(option_hash, environment_options, (VALUE)&options);
494
+
495
+ MDB_env* env;
496
+ check(mdb_env_create(&env));
497
+
498
+ Environment* environment;
499
+ VALUE venv = Data_Make_Struct(cEnvironment, Environment, environment_mark, environment_free, environment);
500
+ environment->env = env;
501
+ environment->thread_txn_hash = rb_hash_new();
502
+ environment->txn_thread_hash = rb_hash_new();
503
+
504
+ if (options.maxreaders > 0)
505
+ check(mdb_env_set_maxreaders(env, options.maxreaders));
506
+ if (options.mapsize > 0)
507
+ check(mdb_env_set_mapsize(env, options.mapsize));
508
+
509
+ check(mdb_env_set_maxdbs(env, options.maxdbs <= 0 ? 1 : options.maxdbs));
510
+ VALUE expanded_path = rb_file_expand_path(path, Qnil);
511
+ check(mdb_env_open(env, StringValueCStr(expanded_path), options.flags, options.mode));
512
+
513
+ if (rb_block_given_p())
514
+ return rb_ensure(rb_yield, venv, environment_close, venv);
515
+
516
+ return venv;
517
+ }
518
+
519
+ /**
520
+ * @overload flags
521
+ * Return the flags that are set in this environment.
522
+ * @return [Array] Array of flag symbols
523
+ * The environment flags are:
524
+ * * +:fixedmap+ Use a fixed address for the mmap region.
525
+ * * +:nosubdir+ By default, MDB creates its environment in a directory whose pathname is given in +path+, and creates its data and lock files under that directory. With this option, path is used as-is for the database main data file. The database lock file is the path with "-lock" appended.
526
+ * * +:nosync+ Don't flush system buffers to disk when committing a transaction. This optimization means a system crash can corrupt the database or lose the last transactions if buffers are not yet flushed to disk. The risk is governed by how often the system flushes dirty buffers to disk and how often {Environment#sync} is called. However, if the filesystem preserves write order and the +:writemap+ flag is not used, transactions exhibit ACI (atomicity, consistency, isolation) properties and only lose D (durability). That is, database integrity is maintained, but a system crash may undo the final transactions. Note that +:nosync + :writemap+ leaves the system with no hint for when to write transactions to disk, unless {Environment#sync} is called. +:mapasync + :writemap+ may be preferable.
527
+ * * +:rdonly+ Open the environment in read-only mode. No write operations will be allowed. MDB will still modify the lock file - except on read-only filesystems, where MDB does not use locks.
528
+ * * +:nometasync+ Flush system buffers to disk only once per transaction, omit the metadata flush. Defer that until the system flushes files to disk, or next non-MDB_RDONLY commit or {Environment#sync}. This optimization maintains database integrity, but a system crash may undo the last committed transaction. That is, it preserves the ACI (atomicity, consistency, isolation) but not D (durability) database property.
529
+ * * +:writemap+ Use a writeable memory map unless +:rdonly+ is set. This is faster and uses fewer mallocs, but loses protection from application bugs like wild pointer writes and other bad updates into the database. Incompatible with nested transactions.
530
+ * * +:mapasync+ When using +:writemap+, use asynchronous flushes to disk. As with +:nosync+, a system crash can then corrupt the database or lose the last transactions. Calling {Environment#sync} ensures on-disk database integrity until next commit.
531
+ * * +:notls+ Don't use thread-local storage.
532
+ * @example
533
+ * env = LMDB.new "abc", :writemap => true, :nometasync => true
534
+ * env.flags #=> [:writemap, :nometasync]
535
+ */
536
+ static VALUE environment_flags(VALUE self) {
537
+ unsigned int flags;
538
+ ENVIRONMENT(self, environment);
539
+ check(mdb_env_get_flags(environment->env, &flags));
540
+
541
+ VALUE ret = rb_ary_new();
542
+ #define FLAG(const, name) if (flags & MDB_##const) rb_ary_push(ret, ID2SYM(rb_intern(#name)));
543
+ #include "env_flags.h"
544
+ #undef FLAG
545
+
546
+ return ret;
547
+ }
548
+
549
+ /**
550
+ * @overload path
551
+ * Return the path to the database environment files
552
+ * @return [String] the path that was used to open the environment.
553
+ */
554
+ static VALUE environment_path(VALUE self) {
555
+ const char* path;
556
+ ENVIRONMENT(self, environment);
557
+ check(mdb_env_get_path(environment->env, &path));
558
+ return rb_str_new2(path);
559
+ }
560
+
561
+ static VALUE environment_set_mapsize(VALUE self, VALUE size) {
562
+ ENVIRONMENT(self, environment);
563
+ check(mdb_env_set_mapsize(environment->env, NUM2LONG(size)));
564
+ return Qnil;
565
+ }
566
+
567
+ static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set) {
568
+ ENVIRONMENT(self, environment);
569
+
570
+ int i;
571
+ for (i = 0; i < argc; ++i) {
572
+ ID id = rb_to_id(argv[i]);
573
+
574
+ if (0) {}
575
+ #define FLAG(const, name) else if (id == rb_intern(#name)) check(mdb_env_set_flags(environment->env, MDB_##const, set));
576
+ #include "env_flags.h"
577
+ #undef FLAG
578
+ else
579
+ rb_raise(cError, "Invalid option %s", StringValueCStr(argv[i]));
580
+ }
581
+ return Qnil;
582
+ }
583
+
584
+ /**
585
+ * @overload set_flags(flags)
586
+ * Set one or more flags in the environment. The available flags are defined in {Environment#flags}.
587
+ * @see Environment#flags
588
+ * @param [Array] flags Array of flag names (symbols) to set
589
+ * @return nil
590
+ * @raise [Error] if an invalid flag name is specified
591
+ * @example
592
+ * env.set_flags :nosync, :writemap
593
+ */
594
+ static VALUE environment_set_flags(int argc, VALUE* argv, VALUE self) {
595
+ environment_change_flags(argc, argv, self, 1);
596
+ return Qnil;
597
+ }
598
+
599
+ /**
600
+ * @overload clear_flags(flags)
601
+ * Clear one or more flags in the environment. The available flags are defined in {Environment#flags}.
602
+ * @see Environment#flags
603
+ * @param [Array] flags Array of flag names (symbols) to clear
604
+ * @return nil
605
+ * @raise [Error] if an invalid flag name is specified
606
+ * @example
607
+ * env.clear_flags :nosync, :writemap
608
+ */
609
+ static VALUE environment_clear_flags(int argc, VALUE* argv, VALUE self) {
610
+ environment_change_flags(argc, argv, self, 0);
611
+ return Qnil;
612
+ }
613
+
614
+ /**
615
+ * @overload active_txn
616
+ * @return [Transaction] the current active transaction on this thread in the environment.
617
+ * @example
618
+ * env.transaction do |t|
619
+ * active = env.active_txn
620
+ * # active should equal t
621
+ * end
622
+ */
623
+ static VALUE environment_active_txn(VALUE self) {
624
+ ENVIRONMENT(self, environment);
625
+ return rb_hash_aref(environment->thread_txn_hash, rb_thread_current());
626
+ }
627
+
628
+ static void environment_set_active_txn(VALUE self, VALUE thread, VALUE txn) {
629
+ ENVIRONMENT(self, environment);
630
+
631
+ if (NIL_P(txn)) {
632
+ VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
633
+ if (!NIL_P(oldtxn)) {
634
+ rb_hash_delete(environment->thread_txn_hash, thread);
635
+ rb_hash_delete(environment->txn_thread_hash, oldtxn);
636
+ }
637
+ } else {
638
+ VALUE oldtxn = rb_hash_aref(environment->thread_txn_hash, thread);
639
+ if (!NIL_P(oldtxn)) {
640
+ rb_hash_delete(environment->txn_thread_hash, oldtxn);
641
+ }
642
+ rb_hash_aset(environment->txn_thread_hash, txn, thread);
643
+ rb_hash_aset(environment->thread_txn_hash, thread, txn);
644
+ }
645
+ }
646
+
647
+
648
+ static MDB_txn* active_txn(VALUE self) {
649
+ VALUE vtxn = environment_active_txn(self);
650
+ if (NIL_P(vtxn))
651
+ return 0;
652
+ TRANSACTION(vtxn, transaction);
653
+ if (!transaction->txn)
654
+ rb_raise(cError, "Transaction is terminated");
655
+ if (transaction->thread != rb_thread_current())
656
+ rb_raise(cError, "Wrong thread");
657
+ return transaction->txn;
658
+ }
659
+
660
+ static MDB_txn* need_txn(VALUE self) {
661
+ MDB_txn* txn = active_txn(self);
662
+ if (!txn)
663
+ rb_raise(cError, "No active transaction");
664
+ return txn;
665
+ }
666
+
667
+ /**
668
+ * @overload transaction(readonly)
669
+ * Begin a transaction. Takes a block to run the body of the
670
+ * transaction. A transaction commits when it exits the block successfully.
671
+ * A transaction aborts when it raises an exception or calls
672
+ * {Transaction#abort}.
673
+ * @param [Boolean] readonly This transaction will not perform any
674
+ * write operations
675
+ * @note Transactions can be nested.
676
+ * @yield [txn] The block to be executed with the body of the transaction.
677
+ * @yieldparam txn [Transaction] An optional transaction argument
678
+ * @example
679
+ * db = env.database "mydata"
680
+ * env.transaction do |txn1|
681
+ * db['a'] = 1
682
+ * env.transaction do |txn2|
683
+ * # txn2 is nested in txn1
684
+ * db['a'] = 2
685
+ * db['a'] #=> 2
686
+ * txn2.abort
687
+ * end
688
+ * db['a'] #=> 1
689
+ * env.transaction do
690
+ * db['a'] = 3
691
+ * end
692
+ * end
693
+ * db['a'] #=> 3
694
+ */
695
+ static VALUE environment_transaction(int argc, VALUE *argv, VALUE self) {
696
+ rb_need_block();
697
+
698
+ VALUE readonly;
699
+ rb_scan_args(argc, argv, "01", &readonly);
700
+ unsigned int flags = RTEST(readonly) ? MDB_RDONLY : 0;
701
+
702
+ return with_transaction(self, rb_yield, Qnil, flags);
703
+ }
704
+
705
+ static void database_mark(Database* database) {
706
+ rb_gc_mark(database->env);
707
+ }
708
+
709
+ #define METHOD database_flags
710
+ #define FILE "dbi_flags.h"
711
+ #include "flag_parser.h"
712
+ #undef METHOD
713
+ #undef FILE
714
+
715
+ /**
716
+ * @overload database(name, options)
717
+ * Opens a database within the environment.
718
+ *
719
+ * Note that a database is opened or created within a transaction. If
720
+ * the open creates a new database, the database is not available for
721
+ * other operations in other transactions until the transaction that
722
+ * is creating the database commits. If the transaction creating the
723
+ * database aborts, the database is not created.
724
+ * @return [Database] newly-opened database
725
+ * @raise [Error] if there is an error opening the database
726
+ * @param [String] name Optional name for the database to be opened.
727
+ * @param [Hash] options Options for the database.
728
+ * @option options [Boolean] :reversekey Keys are strings to be
729
+ * compared in reverse order, from the end of the strings to the
730
+ * beginning. By default, Keys are treated as strings and
731
+ * compared from beginning to end.
732
+ * @option options [Boolean] :dupsort Duplicate keys may be used in
733
+ * the database. (Or, from another perspective, keys may have
734
+ * multiple data items, stored in sorted order.) By default keys
735
+ * must be unique and may have only a single data item.
736
+ * @option options [Boolean] :integerkey Keys are binary integers in
737
+ * native byte order.
738
+ * @option options [Boolean] :dupfixed This flag may only be used in
739
+ * combination with +:dupsort+. This option tells the library
740
+ * that the data items for this database are all the same size,
741
+ * which allows further optimizations in storage and retrieval.
742
+ * @option options [Boolean] :integerdup This option specifies that
743
+ * duplicate data items are also integers, and should be sorted
744
+ * as such.
745
+ * @option options [Boolean] :reversedup This option specifies that
746
+ * duplicate data items should be compared as strings in reverse
747
+ * order.
748
+ * @option options [Boolean] :create Create the named database if it
749
+ * doesn't exist. This option is not allowed in a read-only
750
+ * transaction or a read-only environment.
751
+ */
752
+ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
753
+ ENVIRONMENT(self, environment);
754
+ if (!active_txn(self))
755
+ return call_with_transaction(self, self, "database", argc, argv, 0);
756
+
757
+ VALUE name, option_hash;
758
+ rb_scan_args(argc, argv, "02", &name, &option_hash);
759
+
760
+ int flags = 0;
761
+ if (!NIL_P(option_hash))
762
+ rb_hash_foreach(option_hash, database_flags, (VALUE)&flags);
763
+
764
+ MDB_dbi dbi;
765
+ check(mdb_dbi_open(need_txn(self), NIL_P(name) ? 0 : StringValueCStr(name), flags, &dbi));
766
+
767
+ Database* database;
768
+ VALUE vdb = Data_Make_Struct(cDatabase, Database, database_mark, free, database);
769
+ database->dbi = dbi;
770
+ database->env = self;
771
+
772
+ return vdb;
773
+ }
774
+
775
+ /**
776
+ * @overload stat
777
+ * Return useful statistics about a database.
778
+ * @return [Hash] the statistics
779
+ * * +:psize+ Size of a database page
780
+ * * +:depth+ Depth (height) of the B-tree
781
+ * * +:branch_pages+ Number of internal (non-leaf) pages
782
+ * * +:leaf_pages+ Number of leaf pages
783
+ * * +:overflow_pages+ Number of overflow pages
784
+ * * +:entries+ Number of data items
785
+ */
786
+ static VALUE database_stat(VALUE self) {
787
+ DATABASE(self, database);
788
+ if (!active_txn(database->env))
789
+ return call_with_transaction(database->env,
790
+ self, "stat", 0, 0, MDB_RDONLY);
791
+
792
+ MDB_stat stat;
793
+ check(mdb_stat(need_txn(database->env), database->dbi, &stat));
794
+ return stat2hash(&stat);
795
+ }
796
+
797
+ /**
798
+ * @overload flags
799
+ * Return the flags used to open the database.
800
+ * @return [Hash] The flags.
801
+ */
802
+ static VALUE database_get_flags(VALUE self) {
803
+ DATABASE(self, database);
804
+ if (!active_txn(database->env))
805
+ return call_with_transaction(database->env,
806
+ self, "flags", 0, 0, MDB_RDONLY);
807
+ unsigned int flags;
808
+ check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
809
+ return flags2hash(flags);
810
+ }
811
+
812
+ /* XXX these two could probably also be macro'd, or maybe not i dunno */
813
+
814
+ /**
815
+ * @overload dupsort?
816
+ * Returns whether the database is in +:dupsort+ mode.
817
+ * @return [true, false]
818
+ */
819
+ static VALUE database_is_dupsort(VALUE self) {
820
+ DATABASE(self, database);
821
+ if (!active_txn(database->env))
822
+ return call_with_transaction(database->env, self,
823
+ "dupsort?", 0, 0, MDB_RDONLY);
824
+ unsigned int flags;
825
+ check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
826
+
827
+ return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
828
+ }
829
+
830
+ /**
831
+ * @overload dupfixed?
832
+ * Returns whether the database is in +:dupfixed+ mode.
833
+ * @return [true, false]
834
+ */
835
+ static VALUE database_is_dupfixed(VALUE self) {
836
+ DATABASE(self, database);
837
+ if (!active_txn(database->env))
838
+ return call_with_transaction(database->env, self,
839
+ "dupfixed?", 0, 0, MDB_RDONLY);
840
+ unsigned int flags;
841
+ check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
842
+
843
+ return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
844
+ }
845
+
846
+ /**
847
+ * @overload drop
848
+ * Remove a database from the environment.
849
+ * @return nil
850
+ * @note The drop happens transactionally.
851
+ */
852
+ static VALUE database_drop(VALUE self) {
853
+ DATABASE(self, database);
854
+ if (!active_txn(database->env))
855
+ return call_with_transaction(database->env, self, "drop", 0, 0, 0);
856
+ check(mdb_drop(need_txn(database->env), database->dbi, 1));
857
+ return Qnil;
858
+ }
859
+
860
+ /**
861
+ * @overload clear
862
+ * Empty out the database
863
+ * @return nil
864
+ * @note The clear happens transactionally.
865
+ */
866
+ static VALUE database_clear(VALUE self) {
867
+ DATABASE(self, database);
868
+ if (!active_txn(database->env))
869
+ return call_with_transaction(database->env, self, "clear", 0, 0, 0);
870
+ check(mdb_drop(need_txn(database->env), database->dbi, 0));
871
+ return Qnil;
872
+ }
873
+
874
+ /**
875
+ * @overload get(key)
876
+ * Retrieves one value associated with this key.
877
+ * This function retrieves key/data pairs from the database. If the
878
+ * database supports duplicate keys (+:dupsort+) then the first data
879
+ * item for the key will be returned. Retrieval of other items
880
+ * requires the use of {#cursor}.
881
+ * @param key The key of the record to retrieve.
882
+ */
883
+ static VALUE database_get(VALUE self, VALUE vkey) {
884
+ DATABASE(self, database);
885
+ if (!active_txn(database->env))
886
+ return call_with_transaction(database->env, self, "get", 1, &vkey, MDB_RDONLY);
887
+
888
+ vkey = StringValue(vkey);
889
+ MDB_val key, value;
890
+ key.mv_size = RSTRING_LEN(vkey);
891
+ key.mv_data = RSTRING_PTR(vkey);
892
+
893
+ int ret = mdb_get(need_txn(database->env), database->dbi, &key, &value);
894
+ if (ret == MDB_NOTFOUND)
895
+ return Qnil;
896
+ check(ret);
897
+ return rb_str_new(value.mv_data, value.mv_size);
898
+ }
899
+
900
+ #define METHOD database_put_flags
901
+ #define FILE "put_flags.h"
902
+ #include "flag_parser.h"
903
+ #undef METHOD
904
+ #undef FILE
905
+
906
+ /**
907
+ * @overload put(key, value, options)
908
+ * Stores items into a database.
909
+ * This function stores key/value pairs in the database. The default
910
+ * behavior is to enter the new key/value pair, replacing any
911
+ * previously existing key if duplicates are disallowed, or adding a
912
+ * duplicate data item if duplicates are allowed (+:dupsort+).
913
+ * @param key The key of the record to set
914
+ * @param value The value to insert for this key
915
+ * @option options [Boolean] :nodupdata Enter the new key/value
916
+ * pair only if it does not already appear in the database. This
917
+ * flag may only be specified if the database was opened with
918
+ * +:dupsort+. The function will raise an {Error} if the
919
+ * key/data pair already appears in the database.
920
+ * @option options [Boolean] :nooverwrite Enter the new key/value
921
+ * pair only if the key does not already appear in the
922
+ * database. The function will raise an {Error] if the key
923
+ * already appears in the database, even if the database
924
+ * supports duplicates (+:dupsort+).
925
+ * @option options [Boolean] :append Append the given key/data pair
926
+ * to the end of the database. No key comparisons are
927
+ * performed. This option allows fast bulk loading when keys are
928
+ * already known to be in the correct order. Loading unsorted
929
+ * keys with this flag will cause data corruption.
930
+ * @option options [Boolean] :appenddup As above, but for sorted dup
931
+ * data.
932
+ */
933
+ static VALUE database_put(int argc, VALUE *argv, VALUE self) {
934
+ DATABASE(self, database);
935
+ if (!active_txn(database->env))
936
+ return call_with_transaction(database->env, self, "put", argc, argv, 0);
937
+
938
+ VALUE vkey, vval, option_hash;
939
+ rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
940
+
941
+ int flags = 0;
942
+ if (!NIL_P(option_hash))
943
+ rb_hash_foreach(option_hash, database_put_flags, (VALUE)&flags);
944
+
945
+ vkey = StringValue(vkey);
946
+ vval = StringValue(vval);
947
+
948
+ MDB_val key, value;
949
+ key.mv_size = RSTRING_LEN(vkey);
950
+ key.mv_data = RSTRING_PTR(vkey);
951
+ value.mv_size = RSTRING_LEN(vval);
952
+ value.mv_data = RSTRING_PTR(vval);
953
+
954
+ check(mdb_put(need_txn(database->env), database->dbi, &key, &value, flags));
955
+ return Qnil;
956
+ }
957
+
958
+ /**
959
+ * @overload delete(key, value=nil)
960
+ *
961
+ * Deletes records from the database. This function removes
962
+ * key/data pairs from the database. If the database does not support
963
+ * sorted duplicate data items (+:dupsort+) the value parameter is
964
+ * ignored. If the database supports sorted duplicates and the value
965
+ * parameter is +nil+, all of the duplicate data items for the key will
966
+ * be deleted. Otherwise, if the data parameter is non-nil only the
967
+ * matching data item will be deleted.
968
+ *
969
+ * @param key The key of the record to delete.
970
+ * @param value The optional value of the record to delete.
971
+ * @raise [Error] if the specified key/value pair is not in the database.
972
+ */
973
+ static VALUE database_delete(int argc, VALUE *argv, VALUE self) {
974
+ DATABASE(self, database);
975
+ if (!active_txn(database->env))
976
+ return call_with_transaction(database->env, self, "delete", argc, argv, 0);
977
+
978
+ VALUE vkey, vval;
979
+ rb_scan_args(argc, argv, "11", &vkey, &vval);
980
+
981
+ vkey = StringValue(vkey);
982
+
983
+ MDB_val key;
984
+ key.mv_size = RSTRING_LEN(vkey);
985
+ key.mv_data = RSTRING_PTR(vkey);
986
+
987
+ if (NIL_P(vval)) {
988
+ check(mdb_del(need_txn(database->env), database->dbi, &key, 0));
989
+ } else {
990
+ vval = StringValue(vval);
991
+ MDB_val value;
992
+ value.mv_size = RSTRING_LEN(vval);
993
+ value.mv_data = RSTRING_PTR(vval);
994
+ check(mdb_del(need_txn(database->env), database->dbi, &key, &value));
995
+ }
996
+
997
+ return Qnil;
998
+ }
999
+
1000
+ static void cursor_free(Cursor* cursor) {
1001
+ if (cursor->cur) {
1002
+ rb_warn("Memory leak - Garbage collecting open cursor");
1003
+ // mdb_cursor_close(cursor->cur);
1004
+ }
1005
+
1006
+ free(cursor);
1007
+ }
1008
+
1009
+ static void cursor_check(Cursor* cursor) {
1010
+ if (!cursor->cur)
1011
+ rb_raise(cError, "Cursor is closed");
1012
+ }
1013
+
1014
+ static void cursor_mark(Cursor* cursor) {
1015
+ rb_gc_mark(cursor->db);
1016
+ }
1017
+
1018
+ /**
1019
+ * @overload close
1020
+ * Close a cursor. The cursor must not be used again after this call.
1021
+ */
1022
+ static VALUE cursor_close(VALUE self) {
1023
+ CURSOR(self, cursor);
1024
+ mdb_cursor_close(cursor->cur);
1025
+ cursor->cur = 0;
1026
+ return Qnil;
1027
+ }
1028
+
1029
+ /**
1030
+ * @overload cursor
1031
+ * Create a cursor to iterate through a database. Uses current
1032
+ * transaction, if any. Otherwise, if called with a block,
1033
+ * creates a new transaction for the scope of the block.
1034
+ * Otherwise, fails.
1035
+ *
1036
+ * @see Cursor
1037
+ * @yield [cursor] A block to be executed with the cursor.
1038
+ * @yieldparam cursor [Cursor] The cursor to be used to iterate
1039
+ * @example
1040
+ * db = env.database "abc"
1041
+ * db.cursor do |c|
1042
+ * key, value = c.next
1043
+ * puts "#{key}: #{value}"
1044
+ * end
1045
+ */
1046
+ static VALUE database_cursor(VALUE self) {
1047
+ DATABASE(self, database);
1048
+ if (!active_txn(database->env)) {
1049
+ if (!rb_block_given_p()) {
1050
+ rb_raise(cError, "Must call with block or active transaction.");
1051
+ }
1052
+ return call_with_transaction(database->env, self, "cursor", 0, 0, 0);
1053
+ }
1054
+
1055
+ MDB_cursor* cur;
1056
+ check(mdb_cursor_open(need_txn(database->env), database->dbi, &cur));
1057
+
1058
+ Cursor* cursor;
1059
+ VALUE vcur = Data_Make_Struct(cCursor, Cursor, cursor_mark, cursor_free, cursor);
1060
+ cursor->cur = cur;
1061
+ cursor->db = self;
1062
+
1063
+ if (rb_block_given_p()) {
1064
+ int exception;
1065
+ VALUE ret = rb_protect(rb_yield, vcur, &exception);
1066
+ if (exception) {
1067
+ cursor_close(vcur);
1068
+ rb_jump_tag(exception);
1069
+ }
1070
+ cursor_close(vcur);
1071
+ return ret;
1072
+ }
1073
+ else {
1074
+ VALUE vtxn = environment_active_txn(database->env);
1075
+ if (NIL_P(vtxn)) {
1076
+ rb_fatal("Internal error: transaction finished unexpectedly.");
1077
+ }
1078
+ else {
1079
+ TRANSACTION(vtxn, txn);
1080
+ rb_ary_push(txn->cursors, vcur);
1081
+ }
1082
+ }
1083
+
1084
+ return vcur;
1085
+ }
1086
+
1087
+ /**
1088
+ * @overload env
1089
+ * @return [Environment] the environment to which this database belongs.
1090
+ */
1091
+ static VALUE database_env(VALUE self) {
1092
+ DATABASE(self, database);
1093
+ return database->env;
1094
+ }
1095
+
1096
+ /**
1097
+ * @overload first
1098
+ * Position the cursor to the first record in the database, and
1099
+ * return its value.
1100
+ * @return [Array,nil] The [key, value] pair for the first record, or
1101
+ * nil if no record
1102
+ */
1103
+ static VALUE cursor_first(VALUE self) {
1104
+ CURSOR(self, cursor);
1105
+ MDB_val key, value;
1106
+
1107
+ check(mdb_cursor_get(cursor->cur, &key, &value, MDB_FIRST));
1108
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1109
+ }
1110
+
1111
+ /**
1112
+ * @overload last
1113
+ * Position the cursor to the last record in the database, and
1114
+ * return its value.
1115
+ * @return [Array,nil] The [key, value] pair for the last record, or
1116
+ * nil if no record.
1117
+ */
1118
+ static VALUE cursor_last(VALUE self) {
1119
+ CURSOR(self, cursor);
1120
+ MDB_val key, value;
1121
+
1122
+ check(mdb_cursor_get(cursor->cur, &key, &value, MDB_LAST));
1123
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1124
+ }
1125
+
1126
+ /**
1127
+ * @overload prev
1128
+ * Position the cursor to the previous record in the database, and
1129
+ * return its value.
1130
+ * @return [Array,nil] The [key, value] pair for the previous record, or
1131
+ * nil if no previous record.
1132
+ */
1133
+ static VALUE cursor_prev(VALUE self) {
1134
+ CURSOR(self, cursor);
1135
+ MDB_val key, value;
1136
+
1137
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_PREV);
1138
+ if (ret == MDB_NOTFOUND)
1139
+ return Qnil;
1140
+ check(ret);
1141
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1142
+ }
1143
+
1144
+ /**
1145
+ * @overload next nodup = nil
1146
+ * Position the cursor to the next record in the database, and
1147
+ * return its value.
1148
+ * @param nodup [true, false] If true, skip over duplicate records.
1149
+ * @return [Array,nil] The [key, value] pair for the next record, or
1150
+ * nil if no next record.
1151
+ */
1152
+ static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
1153
+ CURSOR(self, cursor);
1154
+ VALUE nodup;
1155
+ MDB_val key, value;
1156
+ MDB_cursor_op op = MDB_NEXT;
1157
+
1158
+ rb_scan_args(argc, argv, "01", &nodup);
1159
+
1160
+ if (RTEST(nodup))
1161
+ op = MDB_NEXT_NODUP;
1162
+
1163
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1164
+ if (ret == MDB_NOTFOUND)
1165
+ return Qnil;
1166
+ check(ret);
1167
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1168
+ rb_str_new(value.mv_data, value.mv_size));
1169
+ }
1170
+
1171
+ /**
1172
+ * @overload next_range
1173
+ * Position the cursor to the next record in the database, and
1174
+ * return its value if the record's key is less than or equal to
1175
+ * the specified key, or nil otherwise.
1176
+ * @param key [#to_s] The key to serve as the upper bound
1177
+ * @return [Array,nil] The [key, value] pair for the next record, or
1178
+ * nil if no next record or the next record is out of the range.
1179
+ */
1180
+ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
1181
+ CURSOR(self, cursor);
1182
+ MDB_val key, value, ub_key;
1183
+
1184
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_NEXT);
1185
+ if (ret == MDB_NOTFOUND)
1186
+ return Qnil;
1187
+ check(ret);
1188
+
1189
+ ub_key.mv_size = RSTRING_LEN(upper_bound_key);
1190
+ ub_key.mv_data = StringValuePtr(upper_bound_key);
1191
+
1192
+ MDB_txn *txn = mdb_cursor_txn(cursor->cur);
1193
+ MDB_dbi dbi = mdb_cursor_dbi(cursor->cur);
1194
+
1195
+ if (mdb_cmp(txn, dbi, &key, &ub_key) <= 0) {
1196
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1197
+ } else {
1198
+ return Qnil;
1199
+ }
1200
+ }
1201
+
1202
+ /**
1203
+ * @overload set(key, value = nil)
1204
+ * Set the cursor to a specified key, optionally at the specified
1205
+ * value if the database was opened with +:dupsort+.
1206
+ * @param key [#to_s] The key to which the cursor should be positioned
1207
+ * @param value [nil, #to_s] The optional value (+:dupsort+ only)
1208
+ * @return [Array] The +[key, value]+ pair to which the cursor now points.
1209
+ */
1210
+ static VALUE cursor_set(int argc, VALUE* argv, VALUE self) {
1211
+ CURSOR(self, cursor);
1212
+ VALUE vkey, vval;
1213
+ MDB_val key, value;
1214
+ MDB_cursor_op op = MDB_SET_KEY;
1215
+ int ret;
1216
+
1217
+ rb_scan_args(argc, argv, "11", &vkey, &vval);
1218
+
1219
+ key.mv_size = RSTRING_LEN(vkey);
1220
+ key.mv_data = StringValuePtr(vkey);
1221
+
1222
+ if (!NIL_P(vval)) {
1223
+ op = MDB_GET_BOTH;
1224
+ value.mv_size = RSTRING_LEN(vval);
1225
+ value.mv_data = StringValuePtr(vval);
1226
+ }
1227
+
1228
+ ret = mdb_cursor_get(cursor->cur, &key, &value, op);
1229
+
1230
+ if (!NIL_P(vval) && ret == MDB_NOTFOUND)
1231
+ return Qnil;
1232
+
1233
+ check(ret);
1234
+
1235
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
1236
+ rb_str_new(value.mv_data, value.mv_size));
1237
+ }
1238
+
1239
+ /**
1240
+ * @overload set_range(key)
1241
+ * Set the cursor at the first key greater than or equal to a specified key.
1242
+ * @param key The key to which the cursor should be positioned
1243
+ * @return [Array] The [key, value] pair to which the cursor now points.
1244
+ */
1245
+ static VALUE cursor_set_range(VALUE self, VALUE vkey) {
1246
+ CURSOR(self, cursor);
1247
+ MDB_val key, value;
1248
+
1249
+ key.mv_size = RSTRING_LEN(vkey);
1250
+ key.mv_data = StringValuePtr(vkey);
1251
+
1252
+ check(mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE));
1253
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1254
+ }
1255
+
1256
+ /**
1257
+ * @overload get
1258
+ * Return the value of the record to which the cursor points.
1259
+ * @return [Array] The [key, value] pair for the current record.
1260
+ */
1261
+
1262
+ static VALUE cursor_get(VALUE self) {
1263
+ CURSOR(self, cursor);
1264
+
1265
+ MDB_val key, value;
1266
+ int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_GET_CURRENT);
1267
+ if (ret == MDB_NOTFOUND)
1268
+ return Qnil;
1269
+ check(ret);
1270
+ return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
1271
+ }
1272
+
1273
+ #define METHOD cursor_put_flags
1274
+ #define FILE "cursor_put_flags.h"
1275
+ #include "flag_parser.h"
1276
+ #undef METHOD
1277
+ #undef FILE
1278
+
1279
+ /**
1280
+ * @overload put(key, value, options)
1281
+ * Store by cursor. This function stores key/data pairs into the
1282
+ * database. If the function fails for any reason, the state of
1283
+ * the cursor will be unchanged. If the function succeeds and an
1284
+ * item is inserted into the database, the cursor is always
1285
+ * positioned to refer to the newly inserted item.
1286
+ * @return nil
1287
+ * @param key The key of the record to set
1288
+ * @param value The value to insert for this key
1289
+ * @option options [Boolean] :current Overwrite the data of the
1290
+ * key/data pair to which the cursor refers with the specified
1291
+ * data item. The +key+ parameter is ignored.
1292
+ * @option options [Boolean] :nodupdata Enter the new key/value
1293
+ * pair only if it does not already appear in the database. This
1294
+ * flag may only be specified if the database was opened with
1295
+ * +:dupsort+. The function will raise an {Error} if the
1296
+ * key/data pair already appears in the database.
1297
+ * @option options [Boolean] :nooverwrite Enter the new key/value
1298
+ * pair only if the key does not already appear in the
1299
+ * database. The function will raise an {Error] if the key
1300
+ * already appears in the database, even if the database
1301
+ * supports duplicates (+:dupsort+).
1302
+ * @option options [Boolean] :append Append the given key/data pair
1303
+ * to the end of the database. No key comparisons are
1304
+ * performed. This option allows fast bulk loading when keys are
1305
+ * already known to be in the correct order. Loading unsorted
1306
+ * keys with this flag will cause data corruption.
1307
+ * @option options [Boolean] :appenddup As above, but for sorted dup
1308
+ * data.
1309
+ */
1310
+ static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
1311
+ CURSOR(self, cursor);
1312
+
1313
+ VALUE vkey, vval, option_hash;
1314
+ rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
1315
+
1316
+ int flags = 0;
1317
+ if (!NIL_P(option_hash))
1318
+ rb_hash_foreach(option_hash, cursor_put_flags, (VALUE)&flags);
1319
+
1320
+ vkey = StringValue(vkey);
1321
+ vval = StringValue(vval);
1322
+
1323
+ MDB_val key, value;
1324
+ key.mv_size = RSTRING_LEN(vkey);
1325
+ key.mv_data = RSTRING_PTR(vkey);
1326
+ value.mv_size = RSTRING_LEN(vval);
1327
+ value.mv_data = RSTRING_PTR(vval);
1328
+
1329
+ check(mdb_cursor_put(cursor->cur, &key, &value, flags));
1330
+ return Qnil;
1331
+ }
1332
+
1333
+ #define METHOD cursor_delete_flags
1334
+ #define FILE "cursor_delete_flags.h"
1335
+ #include "flag_parser.h"
1336
+ #undef METHOD
1337
+ #undef FILE
1338
+
1339
+ /**
1340
+ * @overload delete(options)
1341
+ * Delete current key/data pair.
1342
+ * This function deletes the key/data pair to which the cursor refers.
1343
+ * @option options [Boolean] :nodupdata Delete all of the data
1344
+ * items for the current key. This flag may only be specified
1345
+ * if the database was opened with +:dupsort+.
1346
+ */
1347
+ static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
1348
+ CURSOR(self, cursor);
1349
+
1350
+ VALUE option_hash;
1351
+ rb_scan_args(argc, argv, ":", &option_hash);
1352
+
1353
+ int flags = 0;
1354
+ if (!NIL_P(option_hash))
1355
+ rb_hash_foreach(option_hash, cursor_delete_flags, (VALUE)&flags);
1356
+
1357
+ check(mdb_cursor_del(cursor->cur, flags));
1358
+ return Qnil;
1359
+ }
1360
+
1361
+ /**
1362
+ * @overload cursor_db
1363
+ * @return [Database] the database which this cursor is iterating over.
1364
+ */
1365
+ static VALUE cursor_db(VALUE self) {
1366
+ CURSOR(self, cursor);
1367
+ return cursor->db;
1368
+ }
1369
+
1370
+ /**
1371
+ * @overload count
1372
+ * Return count of duplicates for current key. This call is only
1373
+ * valid on databases that support sorted duplicate data items
1374
+ * +:dupsort+.
1375
+ * @return [Number] count of duplicates
1376
+ */
1377
+ static VALUE cursor_count(VALUE self) {
1378
+ CURSOR(self, cursor);
1379
+ size_t count;
1380
+ check(mdb_cursor_count(cursor->cur, &count));
1381
+ return SIZET2NUM(count);
1382
+ }
1383
+
1384
+ void Init_isomorfeus_hamster_ext() {
1385
+ VALUE mHamster, mIsomorfeus;
1386
+
1387
+ mIsomorfeus = rb_define_module("Isomorfeus");
1388
+
1389
+ /**
1390
+ * Document-module: Hamster
1391
+ *
1392
+ * The Hamster module presents a Ruby API to the OpenLDAP Lightning Memory-mapped Database (LMDB).
1393
+ * @see http://symas.com/mdb/
1394
+ */
1395
+ mHamster = rb_define_module_under(mIsomorfeus, "Hamster");
1396
+ rb_define_const(mHamster, "LIB_VERSION", rb_str_new2(MDB_VERSION_STRING));
1397
+ rb_define_singleton_method(mHamster, "new", environment_new, -1);
1398
+
1399
+ #define VERSION_CONST(name) rb_define_const(mHamster, "LIB_VERSION_"#name, INT2NUM(MDB_VERSION_##name));
1400
+ VERSION_CONST(MAJOR)
1401
+ VERSION_CONST(MINOR)
1402
+ VERSION_CONST(PATCH)
1403
+ #undef VERSION_CONST
1404
+
1405
+ /**
1406
+ * Document-class: LMDB::Error
1407
+ *
1408
+ * A general class of exceptions raised within the LMDB gem.
1409
+ */
1410
+ cError = rb_define_class_under(mHamster, "Error", rb_eRuntimeError);
1411
+ #define OB_ERROR(name) cError_##name = rb_define_class_under(cError, #name, cError);
1412
+ #include "errors.h"
1413
+ #undef OB_ERROR
1414
+
1415
+ /**
1416
+ * Document-class: LMDB::Environment
1417
+ *
1418
+ * The Environment is the root object for all LMDB operations.
1419
+ *
1420
+ * An LMDB "environment" is a collection of one or more "databases"
1421
+ * (key-value tables), along with transactions to modify those
1422
+ * databases and cursors to iterate through them.
1423
+ *
1424
+ * An environment -- and its collection of databases -- is normally
1425
+ * stored in a directory. That directory will contain two files:
1426
+ * * +data.mdb+: all the records in all the databases in the environment
1427
+ * * +lock.mdb+: state of transactions that may be going on in the environment.
1428
+ *
1429
+ * An environment can contain multiple databases. Each of the
1430
+ * databases has a string name ("mydatabase", "db.3.1982"). You use
1431
+ * the database name to open the database within the environment.
1432
+ *
1433
+ * @example The normal pattern for using LMDB in Ruby
1434
+ * env = LMDB.new "databasedir"
1435
+ * db = env.database "databasename"
1436
+ * # ... do things to the database ...
1437
+ * env.close
1438
+ */
1439
+ cEnvironment = rb_define_class_under(mHamster, "Environment", rb_cObject);
1440
+ rb_define_singleton_method(cEnvironment, "new", environment_new, -1);
1441
+ rb_define_method(cEnvironment, "database", environment_database, -1);
1442
+ rb_define_method(cEnvironment, "active_txn", environment_active_txn, 0);
1443
+ rb_define_method(cEnvironment, "close", environment_close, 0);
1444
+ rb_define_method(cEnvironment, "stat", environment_stat, 0);
1445
+ rb_define_method(cEnvironment, "info", environment_info, 0);
1446
+ rb_define_method(cEnvironment, "copy", environment_copy, 1);
1447
+ rb_define_method(cEnvironment, "sync", environment_sync, -1);
1448
+ rb_define_method(cEnvironment, "mapsize=", environment_set_mapsize, 1);
1449
+ rb_define_method(cEnvironment, "set_flags", environment_set_flags, -1);
1450
+ rb_define_method(cEnvironment, "clear_flags", environment_clear_flags, -1);
1451
+ rb_define_method(cEnvironment, "flags", environment_flags, 0);
1452
+ rb_define_method(cEnvironment, "path", environment_path, 0);
1453
+ rb_define_method(cEnvironment, "transaction", environment_transaction, -1);
1454
+
1455
+ /**
1456
+ * Document-class: LMDB::Database
1457
+ *
1458
+ * An LMDB Database is a table of key-value pairs. It is stored as
1459
+ * part of the {Environment}.
1460
+ *
1461
+ * By default, each key in a Database maps to one value. However, a
1462
+ * Database can be configured at creation to allow duplicate keys, in
1463
+ * which case one key will map to multiple values.
1464
+ *
1465
+ * A Database stores the keys in a sorted order. The order can also
1466
+ * be set with options when the database is created.
1467
+ *
1468
+ * The basic operations on a database are to {#put}, {#get}, and
1469
+ * {#delete} records. One can also iterate through the records in a
1470
+ * database using a {Cursor}.
1471
+ *
1472
+ * @example Typical usage
1473
+ * env = LMDB.new "databasedir"
1474
+ * db = env.database "databasename"
1475
+ * db.put "key1", "value1"
1476
+ * db.put "key2", "value2"
1477
+ * db.get "key1" #=> "value1"
1478
+ * env.close
1479
+ */
1480
+ cDatabase = rb_define_class_under(mHamster, "Database", rb_cObject);
1481
+ rb_undef_method(rb_singleton_class(cDatabase), "new");
1482
+ rb_define_method(cDatabase, "stat", database_stat, 0);
1483
+ rb_define_method(cDatabase, "flags", database_get_flags, 0);
1484
+ rb_define_method(cDatabase, "dupsort?", database_is_dupsort, 0);
1485
+ rb_define_method(cDatabase, "dupfixed?", database_is_dupfixed, 0);
1486
+ rb_define_method(cDatabase, "drop", database_drop, 0);
1487
+ rb_define_method(cDatabase, "clear", database_clear, 0);
1488
+ rb_define_method(cDatabase, "get", database_get, 1);
1489
+ rb_define_method(cDatabase, "put", database_put, -1);
1490
+ rb_define_method(cDatabase, "delete", database_delete, -1);
1491
+ rb_define_method(cDatabase, "cursor", database_cursor, 0);
1492
+ rb_define_method(cDatabase, "env", database_env, 0);
1493
+
1494
+ /**
1495
+ * Document-class: LMDB::Transaction
1496
+ *
1497
+ * The LMDB environment supports transactional reads and updates. By
1498
+ * default, these provide the standard ACID (atomicity, consistency,
1499
+ * isolation, durability) behaviors.
1500
+ *
1501
+ * Transactions can be committed or aborted. When a transaction is
1502
+ * committed, all its effects take effect in the database atomically.
1503
+ * When a transaction is aborted, none of its effects take effect.
1504
+ *
1505
+ * Transactions span the entire environment. All the updates made in
1506
+ * the course of an update transaction -- writing records across all
1507
+ * databases, creating databases, and destroying databases -- are
1508
+ * either completed atomically or rolled back.
1509
+ *
1510
+ * Transactions can be nested. A child transaction can be started
1511
+ * within a parent transaction. The child transaction can commit or
1512
+ * abort, at which point the effects of the child become visible to
1513
+ * the parent transaction or not. If the parent aborts, all of the
1514
+ * changes performed in the context of the parent -- including the
1515
+ * changes from a committed child transaction -- are rolled back.
1516
+ *
1517
+ * To create a transaction, call {Environment#transaction} and supply
1518
+ * a block for the code to execute in that transaction.
1519
+ *
1520
+ * @example Typical usage
1521
+ * env = LMDB.new "databasedir"
1522
+ * db1 = env.database "database1"
1523
+ * env.transaction do |parent|
1524
+ * db2 = env.database "database2", :create => true
1525
+ * #=> creates a new database, but it isn't
1526
+ * #=> yet committed to storage
1527
+ * db1['x'] #=> nil
1528
+ * env.transaction do |child1|
1529
+ * db2['a'] = 'b'
1530
+ * db1['x'] = 'y'
1531
+ * end
1532
+ * #=> first child transaction commits
1533
+ * #=> changes are visible within the parent transaction
1534
+ * #=> but are not yet permanent
1535
+ * db1['x'] #=> 'y'
1536
+ * db2['a'] #=> 'a'
1537
+ * env.transaction do |child2|
1538
+ * db2['a'] = 'def'
1539
+ * db1['x'] = 'ghi'
1540
+ * child2.abort
1541
+ * #=> second child transaction aborts and rolls
1542
+ * #=> back its changes
1543
+ * end
1544
+ * db1['x'] #=> 'y'
1545
+ * db2['a'] #=> 'a'
1546
+ * end
1547
+ * #=> parent transaction commits and writes database2
1548
+ * #=> and the updates from transaction child1 to
1549
+ * #=> storage.
1550
+ */
1551
+ cTransaction = rb_define_class_under(mHamster, "Transaction", rb_cObject);
1552
+ rb_undef_method(rb_singleton_class(cTransaction), "new");
1553
+ rb_define_method(cTransaction, "commit", transaction_commit, 0);
1554
+ rb_define_method(cTransaction, "abort", transaction_abort, 0);
1555
+ rb_define_method(cTransaction, "env", transaction_env, 0);
1556
+
1557
+ /**
1558
+ * Document-class: LMDB::Cursor
1559
+ *
1560
+ * A Cursor points to records in a database, and is used to iterate
1561
+ * through the records in the database.
1562
+ *
1563
+ * Cursors are created in the context of a transaction, and should
1564
+ * only be used as long as that transaction is active. In other words,
1565
+ * after you {Transaction#commit} or {Transaction#abort} a transaction,
1566
+ * the cursors created while that transaction was active are no longer
1567
+ * usable.
1568
+ *
1569
+ * To create a cursor, call {Database#cursor} and pass it a block for
1570
+ * that should be performed using the cursor.
1571
+ *
1572
+ * @example Typical usage
1573
+ * env = LMDB.new "databasedir"
1574
+ * db = env.database "databasename"
1575
+ * db.cursor do |cursor|
1576
+ * rl = cursor.last #=> content of the last record
1577
+ * r1 = cursor.first #=> content of the first record
1578
+ * r2 = cursor.next #=> content of the second record
1579
+ * cursor.put "x", "y", current: true
1580
+ * #=> replaces the second record with a new value "y"
1581
+ * end
1582
+ */
1583
+ cCursor = rb_define_class_under(mHamster, "Cursor", rb_cObject);
1584
+ rb_undef_method(rb_singleton_class(cCursor), "new");
1585
+ rb_define_method(cCursor, "close", cursor_close, 0);
1586
+ rb_define_method(cCursor, "get", cursor_get, 0);
1587
+ rb_define_method(cCursor, "first", cursor_first, 0);
1588
+ rb_define_method(cCursor, "last", cursor_last, 0);
1589
+ rb_define_method(cCursor, "next", cursor_next, -1);
1590
+ rb_define_method(cCursor, "next_range", cursor_next_range, 1);
1591
+ rb_define_method(cCursor, "prev", cursor_prev, 0);
1592
+ rb_define_method(cCursor, "set", cursor_set, -1);
1593
+ rb_define_method(cCursor, "set_range", cursor_set_range, 1);
1594
+ rb_define_method(cCursor, "put", cursor_put, -1);
1595
+ rb_define_method(cCursor, "count", cursor_count, 0);
1596
+ rb_define_method(cCursor, "delete", cursor_delete, -1);
1597
+ rb_define_method(cCursor, "database", cursor_db, 0);
1598
+ }