13 /* this protects the global list of tdb objects maintained by libtdb */
14 static pthread_mutex_t big_lock
= PTHREAD_MUTEX_INITIALIZER
;
15 static VALUE cTDB
, cERR
;
16 static st_table
*exc_hash
;
19 /* must be a macro to prevent GC from killing converted 'val's */
20 #define TO_TDB_DATA(data,val) do { \
22 (data).dptr = (unsigned char *)RSTRING_PTR(val); \
23 (data).dsize = RSTRING_LEN(val); \
26 static void init_exc(enum TDB_ERROR ecode
, const char *name
)
28 VALUE exc
= rb_define_class_under(cERR
, name
, cERR
);
29 st_insert(exc_hash
, (st_data_t
)ecode
, (st_data_t
)exc
);
32 static void init_errors(void)
34 cERR
= rb_define_class_under(cTDB
, "ERR", rb_eStandardError
);
35 exc_hash
= st_init_numtable();
37 init_exc(TDB_ERR_CORRUPT
, "CORRUPT");
38 init_exc(TDB_ERR_IO
, "IO");
39 init_exc(TDB_ERR_LOCK
, "LOCK");
40 init_exc(TDB_ERR_OOM
, "OOM");
41 init_exc(TDB_ERR_EXISTS
, "EXISTS"),
42 init_exc(TDB_ERR_NOLOCK
, "NOLOCK");
43 init_exc(TDB_ERR_LOCK_TIMEOUT
, "LOCK_TIMEOUT");
44 init_exc(TDB_ERR_EINVAL
, "EINVAL");
45 init_exc(TDB_ERR_NOEXIST
, "NOEXIST");
46 init_exc(TDB_ERR_RDONLY
, "RDONLY");
47 #ifdef HAVE_CONST_TDB_ERR_NESTING
48 init_exc(TDB_ERR_NESTING
, "NESTING");
49 #endif /* HAVE_CONST_TDB_ERR_NESTING */
52 static void my_raise(struct tdb_context
*tdb
)
54 enum TDB_ERROR ecode
= tdb_error(tdb
);
55 const char *str
= tdb_errorstr(tdb
);
60 rb_bug("attempted to raise with no error");
67 case TDB_ERR_LOCK_TIMEOUT
:
71 #ifdef HAVE_CONST_TDB_ERR_NESTING
73 #endif /* HAVE_CONST_TDB_ERR_NESTING */
74 if (!st_lookup(exc_hash
, (st_data_t
)ecode
, (st_data_t
*)&exc
))
75 rb_bug("no-existent exception: %s\n", str
);
80 static void init_hashes(void)
83 rb_hash_aset(hashes,ID2SYM(rb_intern(#x)),ULONG2NUM((unsigned long)rbtdb_##x))
97 #ifndef HAVE_RB_THREAD_BLOCKING_REGION
98 /* (very) partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
100 typedef VALUE
rb_blocking_function_t(void *);
101 static VALUE
my_tbr(rb_blocking_function_t
*fn
, void *data
)
112 static VALUE
my_tbr(rb_blocking_function_t
*fn
, void *data
)
114 return rb_thread_blocking_region(fn
, data
, RUBY_UBF_IO
, 0);
116 #endif /* HAVE_RUBY_THREAD_BLOCKING_REGION */
118 static void gcfree(void *ptr
)
120 struct tdb_context
*tdb
= ptr
;
122 /* no error checking in GC :< */
124 (void)pthread_mutex_lock(&big_lock
);
125 (void)tdb_close(tdb
);
126 (void)pthread_mutex_unlock(&big_lock
);
130 static VALUE
alloc(VALUE klass
)
132 return Data_Wrap_Struct(klass
, NULL
, gcfree
, NULL
);
135 static struct tdb_context
*db(VALUE self
, int check_opened
)
137 struct tdb_context
*tdb
;
139 Data_Get_Struct(self
, struct tdb_context
, tdb
);
141 if (!tdb
&& check_opened
)
142 rb_raise(rb_eIOError
, "closed database");
153 struct tdb_logging_context
*log_ctx
;
154 tdb_hash_func hash_fn
;
157 static VALUE
nogvl_open(void *ptr
)
159 struct open_args
*o
= ptr
;
160 struct tdb_context
*tdb
;
162 pthread_mutex_lock(&big_lock
);
163 tdb
= tdb_open_ex(o
->name
, o
->hash_size
, o
->tdb_flags
,
164 o
->open_flags
, o
->mode
, o
->log_ctx
, o
->hash_fn
);
165 pthread_mutex_unlock(&big_lock
);
170 static void set_args(VALUE self
, struct open_args
*o
, VALUE opts
)
175 o
->hash_size
= 0; /* default */
176 o
->tdb_flags
= TDB_DEFAULT
;
177 o
->open_flags
= O_RDWR
| O_CREAT
;
184 Check_Type(opts
, T_HASH
);
186 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("hash_size")));
188 o
->hash_size
= NUM2INT(tmp
);
190 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("mode")));
192 o
->mode
= NUM2UINT(tmp
);
194 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("open_flags")));
196 o
->open_flags
= NUM2INT(tmp
);
198 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("tdb_flags")));
200 o
->tdb_flags
= NUM2INT(tmp
);
202 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("hash")));
204 VALUE num
= rb_hash_aref(hashes
, tmp
);
207 tmp
= rb_inspect(tmp
);
208 rb_raise(rb_eArgError
,
209 "`%s' is not a valid hash function",
210 StringValuePtr(tmp
));
213 o
->hash_fn
= (tdb_hash_func
)NUM2ULONG(num
);
216 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("threadsafe")));
218 rb_funcall(self
, rb_intern("threadsafe!"), 0);
224 * TDB.new("/path/to/file") -> TDB
225 * TDB.new("/path/to/file", :hash_size => 666) -> TDB
226 * TDB.new("/path/to/file", :hash => :murmur2) -> TDB
227 * TDB.new("/path/to/file", :open_flags => IO::RDONLY) -> TDB
228 * TDB.new("/path/to/file", :tdb_flags => TDB::NOSYNC) -> TDB
230 * Initializes a TDB context. It takes several options.
232 * :hash_size - the number of buckets, this is the most important tuning
233 * parameter when creating large databases. This parameter only affects
234 * the creation of new databases.
236 * :open_flags - a bit mask of IO flags passed directly to open(2),
237 * File.open-compatible flags are accepted.
239 * :hash - any of the hashes described in Hash_Functions.
240 * This must remain the same for all clients.
242 * :tdb_flags - a bitmask of any combination of TDB::CLEAR_IF_FIRST,
243 * TDB::INTERNAL, TDB::NOLOCK, TDB::NOMMAP, TDB::CONVERT,
244 * TDB::BIGENDIAN, TDB::NOSYNC, TDB::SEQNUM, TDB::VOLATILE,
245 * TDB::ALLOW_NESTING, TDB::DISALLOW_NESTING, TDB::INCOMPATIBLE_HASH
247 * :mode - octal mode mask passed to open(2)
249 static VALUE
init(int argc
, VALUE
*argv
, VALUE self
)
251 struct tdb_context
*tdb
= db(self
, 0);
256 rb_raise(rb_eRuntimeError
, "TDB already initialized");
257 rb_scan_args(argc
, argv
, "11", &path
, &opts
);
258 set_args(self
, &o
, opts
);
261 o
.tdb_flags
|= TDB_INTERNAL
;
263 o
.name
= StringValuePtr(path
);
265 tdb
= (struct tdb_context
*)my_tbr(nogvl_open
, &o
);
272 tdb
= (struct tdb_context
*)my_tbr(nogvl_open
, &o
);
275 rb_sys_fail("tdb_open_ex");
277 DATA_PTR(self
) = tdb
;
282 /* tdb_close can do a lot, including cancel transactions an munmap */
283 static VALUE
nogvl_close(void *ptr
)
285 struct tdb_context
*tdb
= ptr
;
288 pthread_mutex_lock(&big_lock
);
289 rv
= (VALUE
)tdb_close(tdb
);
290 pthread_mutex_unlock(&big_lock
);
295 static VALUE
tdbclose(VALUE self
)
297 struct tdb_context
*tdb
= db(self
, 1);
299 DATA_PTR(self
) = NULL
;
301 if ((int)my_tbr(nogvl_close
, tdb
) == -1)
302 rb_sys_fail("tdb_close");
307 static VALUE
closed(VALUE self
)
309 struct tdb_context
*tdb
= db(self
, 0);
311 return tdb
? Qfalse
: Qtrue
;
314 #ifdef HAVE_RB_THREAD_CALL_WITH_GVL
315 /* missing prototype in ruby.h: */
316 void *rb_thread_call_with_gvl(void *(*func
)(void *), void *data
);
318 static void * my_rb_thread_call_with_gvl(void *(*func
)(void *), void *data
)
320 return (*func
)(data
);
322 #define rb_thread_call_with_gvl my_rb_thread_call_with_gvl
323 #endif /* !HAVE_RB_THREAD_CALL_WITH_GVL */
326 * We avoid the extra malloc/free pair enforced by tdb_fetch. We
327 * use tdb_parse_record to give us pointers to (hopefully) mmap-ed
328 * regions and create a String object directly off that region.
330 struct fetch_parse_args
{
331 struct tdb_context
*tdb
;
341 static VALUE
str_new_tdb_data(TDB_DATA
*val
)
343 return rb_str_new((const char *)val
->dptr
, val
->dsize
);
346 static void *gvl_str_resize(void *data
)
348 struct fetch_parse_args
*f
= data
;
350 rb_str_resize(f
->value
, f
->as
.value_len
);
351 f
->as
.value_ptr
= RSTRING_PTR(f
->value
);
356 static int fetch_parse(TDB_DATA key
, TDB_DATA val
, void *data
)
358 struct fetch_parse_args
*f
= data
;
360 f
->as
.value_len
= val
.dsize
;
361 (void)rb_thread_call_with_gvl(gvl_str_resize
, data
);
362 memcpy(f
->as
.value_ptr
, val
.dptr
, val
.dsize
);
363 f
->as
.value
= f
->value
;
368 static VALUE
nogvl_parse_record(void *ptr
)
370 struct fetch_parse_args
*f
= ptr
;
372 if (tdb_parse_record(f
->tdb
, f
->as
.key
, fetch_parse
, ptr
) == -1)
375 return f
->value
== f
->as
.value
? f
->value
: Qnil
;
378 static VALUE
fetch(int argc
, VALUE
*argv
, VALUE self
)
380 struct fetch_parse_args f
;
383 rb_scan_args(argc
, argv
, "11", &key
, &f
.value
);
384 if (NIL_P(f
.value
)) {
385 f
.value
= rb_str_new(0, 0);
387 StringValue(f
.value
);
388 rb_str_set_len(f
.value
, 0);
392 TO_TDB_DATA(f
.as
.key
, key
);
394 return my_tbr(nogvl_parse_record
, &f
);
398 struct tdb_context
*tdb
;
404 static VALUE
nogvl_store(void *ptr
)
406 struct store_args
*s
= ptr
;
408 return (VALUE
)tdb_store(s
->tdb
, s
->key
, s
->val
, s
->flag
);
411 static VALUE
rbtdb_store(VALUE self
, VALUE key
, VALUE val
, int flag
, int soft
)
416 TO_TDB_DATA(s
.key
, key
);
417 TO_TDB_DATA(s
.val
, val
);
420 if ((int)my_tbr(nogvl_store
, &s
) == -1) {
422 int ecode
= tdb_error(s
.tdb
);
424 if ((flag
== TDB_INSERT
) && (ecode
== TDB_ERR_EXISTS
))
426 if ((flag
== TDB_MODIFY
) && (ecode
== TDB_ERR_NOEXIST
))
435 static VALUE
store(VALUE self
, VALUE key
, VALUE val
)
437 return rbtdb_store(self
, key
, val
, 0, 0);
440 static VALUE
insert_bang(VALUE self
, VALUE key
, VALUE val
)
442 return rbtdb_store(self
, key
, val
, TDB_INSERT
, 0);
445 static VALUE
insert(VALUE self
, VALUE key
, VALUE val
)
447 return rbtdb_store(self
, key
, val
, TDB_INSERT
, 1);
450 static VALUE
modify_bang(VALUE self
, VALUE key
, VALUE val
)
452 return rbtdb_store(self
, key
, val
, TDB_MODIFY
, 0);
455 static VALUE
modify(VALUE self
, VALUE key
, VALUE val
)
457 return rbtdb_store(self
, key
, val
, TDB_MODIFY
, 1);
461 struct tdb_context
*tdb
;
465 static VALUE
nogvl_exists(void *ptr
)
467 struct exists_args
*e
= ptr
;
469 return tdb_exists(e
->tdb
, e
->key
) == 0 ? Qfalse
: Qtrue
;
472 static VALUE
has_key(VALUE self
, VALUE key
)
474 struct exists_args e
;
477 TO_TDB_DATA(e
.key
, key
);
479 return my_tbr(nogvl_exists
, &e
);
482 struct traverse_args
{
483 struct tdb_context
*tdb
;
489 static VALUE
protected_yield(VALUE val
)
491 VALUE
*kv
= (VALUE
*)val
;
493 return rb_yield_values(2, kv
[0], kv
[1]);
496 static void *my_yield(void *data
)
498 struct traverse_args
*t
= data
;
501 kv
[0] = str_new_tdb_data(&t
->key
);
502 kv
[1] = str_new_tdb_data(&t
->val
);
504 rb_protect(protected_yield
, (VALUE
)kv
, &t
->state
);
510 traverse_fn(struct tdb_context
*tdb
, TDB_DATA key
, TDB_DATA val
, void *data
)
512 struct traverse_args
*t
= data
;
516 (void)rb_thread_call_with_gvl(my_yield
, t
);
521 static VALUE
nogvl_traverse(void *ptr
)
523 struct traverse_args
*t
= ptr
;
525 (void)tdb_traverse(t
->tdb
, traverse_fn
, t
);
530 static VALUE
each(VALUE self
)
532 struct traverse_args t
;
537 my_tbr(nogvl_traverse
, &t
);
539 rb_jump_tag(t
.state
);
544 struct tdb_context
*tdb
;
548 static VALUE
nogvl_delete(void *ptr
)
550 struct delete_args
*d
= ptr
;
552 return tdb_delete(d
->tdb
, d
->key
) == 0 ? Qtrue
: Qfalse
;
555 static VALUE
nuke(VALUE self
, VALUE key
)
557 struct delete_args d
;
560 TO_TDB_DATA(d
.key
, key
);
562 return my_tbr(nogvl_delete
, &d
);
565 static VALUE
aref(VALUE self
, VALUE key
)
567 return fetch(1, &key
, self
);
570 static VALUE
delete(int argc
, VALUE
*argv
, VALUE self
)
572 VALUE rc
= fetch(argc
, argv
, self
);
575 if (nuke(self
, argv
[0]) == Qfalse
)
580 static VALUE
lockall(VALUE self
)
582 struct tdb_context
*tdb
= db(self
, 1);
583 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall
, tdb
))
589 static VALUE
trylockall(VALUE self
)
591 struct tdb_context
*tdb
= db(self
, 1);
592 void *fn
= tdb_lockall_nonblock
;
594 if ((int)my_tbr((rb_blocking_function_t
*)fn
, tdb
)) {
595 if (tdb_error(tdb
) == TDB_ERR_LOCK
)
602 static VALUE
unlockall(VALUE self
)
604 struct tdb_context
*tdb
= db(self
, 1);
605 if ((int)my_tbr((rb_blocking_function_t
*)tdb_unlockall
, tdb
))
610 static VALUE
lockall_read(VALUE self
)
612 struct tdb_context
*tdb
= db(self
, 1);
613 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall_read
, tdb
))
618 static VALUE
trylockall_read(VALUE self
)
620 struct tdb_context
*tdb
= db(self
, 1);
621 void *fn
= tdb_lockall_read_nonblock
;
622 if ((int)my_tbr((rb_blocking_function_t
*)fn
, tdb
)) {
623 if (tdb_error(tdb
) == TDB_ERR_LOCK
)
630 static VALUE
unlockall_read(VALUE self
)
632 struct tdb_context
*tdb
= db(self
, 1);
633 if ((int)my_tbr((rb_blocking_function_t
*)tdb_unlockall_read
, tdb
))
638 static VALUE
lockall_mark(VALUE self
)
640 struct tdb_context
*tdb
= db(self
, 1);
641 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall_mark
, tdb
))
646 static VALUE
lockall_unmark(VALUE self
)
648 struct tdb_context
*tdb
= db(self
, 1);
649 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall_unmark
, tdb
))
655 * clears out the database
657 static VALUE
clear(VALUE self
)
659 struct tdb_context
*tdb
= db(self
, 1);
660 if ((int)my_tbr((rb_blocking_function_t
*)tdb_wipe_all
, tdb
))
665 #ifdef HAVE_TDB_REPACK
666 /* repacks a database to reduce fragmentation, available with tdb 1.2.x+ */
667 static VALUE
repack(VALUE self
)
669 struct tdb_context
*tdb
= db(self
, 1);
670 if ((int)my_tbr((rb_blocking_function_t
*)tdb_repack
, tdb
))
674 #endif /* HAVE_TDB_REPACK */
676 void Init_tdb_ext(void)
678 cTDB
= rb_define_class("TDB", rb_cObject
);
680 hashes
= rb_hash_new();
683 * Available hash functions, the key is the name of the hash
684 * and the value is a pointer for internal for usage.
686 rb_define_const(cTDB
, "HASHES", hashes
);
688 rb_define_alloc_func(cTDB
, alloc
);
689 rb_include_module(cTDB
, rb_mEnumerable
);
691 rb_define_method(cTDB
, "initialize", init
, -1);
692 rb_define_method(cTDB
, "close", tdbclose
, 0);
693 rb_define_method(cTDB
, "closed?", closed
, 0);
695 rb_define_method(cTDB
, "fetch", fetch
, -1);
696 rb_define_method(cTDB
, "[]", aref
, 1);
697 rb_define_method(cTDB
, "store", store
, 2);
698 rb_define_method(cTDB
, "[]=", store
, 2);
699 rb_define_method(cTDB
, "insert!", insert_bang
, 2);
700 rb_define_method(cTDB
, "modify!", modify_bang
, 2);
701 rb_define_method(cTDB
, "insert", insert
, 2);
702 rb_define_method(cTDB
, "modify", modify
, 2);
704 rb_define_method(cTDB
, "key?", has_key
, 1);
705 rb_define_method(cTDB
, "has_key?", has_key
, 1);
706 rb_define_method(cTDB
, "include?", has_key
, 1);
707 rb_define_method(cTDB
, "member?", has_key
, 1);
708 rb_define_method(cTDB
, "each", each
, 0);
709 rb_define_method(cTDB
, "nuke!", nuke
, 1);
710 rb_define_method(cTDB
, "delete", delete, -1);
712 rb_define_method(cTDB
, "lockall", lockall
, 0);
713 rb_define_method(cTDB
, "trylockall", trylockall
, 0);
714 rb_define_method(cTDB
, "unlockall", unlockall
, 0);
715 rb_define_method(cTDB
, "lockall_read", lockall_read
, 0);
716 rb_define_method(cTDB
, "trylockall_read", trylockall_read
, 0);
717 rb_define_method(cTDB
, "unlockall_read", unlockall_read
, 0);
718 rb_define_method(cTDB
, "lockall_mark", lockall_mark
, 0);
719 rb_define_method(cTDB
, "lockall_unmark", lockall_unmark
, 0);
720 rb_define_method(cTDB
, "clear", clear
, 0);
721 #ifdef HAVE_TDB_REPACK
722 rb_define_method(cTDB
, "repack", repack
, 0);
723 #endif /* HAVE_TDB_REPACK */
728 /* just a readability place holder */
729 rb_define_const(cTDB
, "DEFAULT", UINT2NUM(TDB_DEFAULT
));
731 /* clear database if we are the only one with it open */
732 rb_define_const(cTDB
, "CLEAR_IF_FIRST", UINT2NUM(TDB_CLEAR_IF_FIRST
));
734 /* don't store on disk, use in-memory database */
735 rb_define_const(cTDB
, "INTERNAL", UINT2NUM(TDB_INTERNAL
));
737 /* don't do any locking */
738 rb_define_const(cTDB
, "NOLOCK", UINT2NUM(TDB_NOLOCK
));
741 rb_define_const(cTDB
, "NOMMAP", UINT2NUM(TDB_NOMMAP
));
743 /* convert endian (internal use) */
744 rb_define_const(cTDB
, "CONVERT", UINT2NUM(TDB_CONVERT
));
746 /* header is big-endian (internal use) */
747 rb_define_const(cTDB
, "BIGENDIAN", UINT2NUM(TDB_BIGENDIAN
));
749 /* don't use synchronous transactions */
750 rb_define_const(cTDB
, "NOSYNC", UINT2NUM(TDB_NOSYNC
));
752 /* maintain a sequence number */
753 rb_define_const(cTDB
, "SEQNUM", UINT2NUM(TDB_SEQNUM
));
755 /* Activate the per-hashchain freelist, default 5 */
756 rb_define_const(cTDB
, "VOLATILE", UINT2NUM(TDB_VOLATILE
));
758 #ifdef TDB_ALLOW_NESTING
759 /* Allow transactions to nest */
760 rb_define_const(cTDB
, "ALLOW_NESTING", UINT2NUM(TDB_ALLOW_NESTING
));
763 #ifdef TDB_DISALLOW_NESTING
764 /* Disallow transactions to nest */
765 rb_define_const(cTDB
, "DISALLOW_NESTING", UINT2NUM(TDB_DISALLOW_NESTING
));
768 #ifdef TDB_INCOMPATIBLE_HASH
769 /* Better hashing, but can't be opened by tdb < 1.2.6. */
770 rb_define_const(cTDB
, "INCOMPATIBLE_HASH", UINT2NUM(TDB_INCOMPATIBLE_HASH
));
772 rbtdb_init_tdb_hash_functions();
776 * Document-class: TDB
779 * tdb = TDB.new("/path/to/file", flags => IO::RDWR|IO::CREAT)
780 * tdb.store("HELLO", "world")
781 * tdb.fetch("HELLO") -> "world"
782 * tdb.delete("HELLO") -> "world"