isomorfeus-hamster 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +98 -0
- data/README.md +7 -0
- data/ext/isomorfeus_hamster_ext/cursor_delete_flags.h +1 -0
- data/ext/isomorfeus_hamster_ext/cursor_put_flags.h +2 -0
- data/ext/isomorfeus_hamster_ext/dbi_flags.h +7 -0
- data/ext/isomorfeus_hamster_ext/env_flags.h +11 -0
- data/ext/isomorfeus_hamster_ext/errors.h +21 -0
- data/ext/isomorfeus_hamster_ext/extconf.rb +22 -0
- data/ext/isomorfeus_hamster_ext/flag_parser.h +14 -0
- data/ext/isomorfeus_hamster_ext/isomorfeus_hamster.c +1598 -0
- data/ext/isomorfeus_hamster_ext/isomorfeus_hamster.h +164 -0
- data/ext/isomorfeus_hamster_ext/lmdb/.gitignore +16 -0
- data/ext/isomorfeus_hamster_ext/lmdb/COPYRIGHT +20 -0
- data/ext/isomorfeus_hamster_ext/lmdb/LICENSE +47 -0
- data/ext/isomorfeus_hamster_ext/lmdb/lmdb.h +1653 -0
- data/ext/isomorfeus_hamster_ext/lmdb/mdb.c +11349 -0
- data/ext/isomorfeus_hamster_ext/lmdb/midl.c +421 -0
- data/ext/isomorfeus_hamster_ext/lmdb/midl.h +200 -0
- data/ext/isomorfeus_hamster_ext/prototypes.sh +4 -0
- data/ext/isomorfeus_hamster_ext/put_flags.h +5 -0
- data/lib/isomorfeus/hamster/database.rb +137 -0
- data/lib/isomorfeus/hamster/marshal.rb +63 -0
- data/lib/isomorfeus/hamster/version.rb +5 -0
- data/lib/isomorfeus-hamster.rb +4 -0
- metadata +125 -0
@@ -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
|
+
}
|