#include "bdb.h"

static ID id_bt_compare, id_bt_prefix, id_dup_compare, id_h_hash;
#if BDB_VERSION >= 40100
static ID id_append_recno;
#endif

#if BDB_VERSION >= 30000
static ID id_feedback;
#endif

static void bdb_mark _((bdb_DB *));

#define GetIdDb(obj, dbst) do {							\
    VALUE th = rb_thread_current();						\
										\
    if (!RTEST(th) || !RBASIC(th)->flags) {					\
	rb_raise(bdb_eFatal, "invalid thread object");				\
    }										\
    (obj) = rb_thread_local_aref(th, bdb_id_current_db); 			\
    if (TYPE(obj) != T_DATA ||						  	\
	RDATA(obj)->dmark != (RUBY_DATA_FUNC)bdb_mark) {		  	\
	rb_raise(bdb_eFatal, "BUG : current_db not set");		  	\
    }									  	\
    Data_Get_Struct(obj, bdb_DB, dbst);					  	\
} while (0)

void
bdb_ary_push(struct ary_st *db_ary, VALUE obj)
{
    if (db_ary->mark) {
        rb_warning("db_ary in mark phase");
        return;
    }
    if (db_ary->len == db_ary->total) {
	if (db_ary->total) {
	    REALLOC_N(db_ary->ptr, VALUE, db_ary->total + 5);
	}
	else {
	    db_ary->ptr = ALLOC_N(VALUE, 5);
	}
	db_ary->total += 5;
    }
    db_ary->ptr[db_ary->len] = obj;
    db_ary->len++;
}

void
bdb_ary_unshift(struct ary_st *db_ary, VALUE obj)
{
    if (db_ary->mark) {
        rb_warning("db_ary in mark phase");
        return;
    }
    if (db_ary->len == db_ary->total) {
	if (db_ary->total) {
	    REALLOC_N(db_ary->ptr, VALUE, db_ary->total + 5);
	}
	else { 
	    db_ary->ptr = ALLOC_N(VALUE, 5);
	}
	db_ary->total += 5;
    }
    if (db_ary->len) {
        MEMMOVE(db_ary->ptr + 1, db_ary->ptr, VALUE, db_ary->len);
    }
    db_ary->len++;
    db_ary->ptr[0] = obj;
}

VALUE
bdb_ary_delete(struct ary_st *db_ary, VALUE val)
{
    int i, pos;

    if (!db_ary->ptr || db_ary->mark) return Qfalse;
    for (pos = 0; pos < db_ary->len; pos++) {
	if (db_ary->ptr[pos] == val) {
	    for (i = pos + 1; i < db_ary->len; i++, pos++) {
		db_ary->ptr[pos] = db_ary->ptr[i];
	    }
	    db_ary->len = pos;
	    return Qtrue;
	}
    }
    return Qfalse;
}

void
bdb_ary_mark(struct ary_st *db_ary)
{
    int i;

    for (i = 0; i < db_ary->len; i++) {
        rb_gc_mark(db_ary->ptr[i]);
    }
}

VALUE
bdb_test_dump(VALUE obj, DBT *key, VALUE a, int type_kv)
{
    bdb_DB *dbst;
    int is_nil = 0;
    VALUE tmp = a;

    Data_Get_Struct(obj, bdb_DB, dbst);
    if (dbst->filter[type_kv]) {
	if (FIXNUM_P(dbst->filter[type_kv])) {
	    tmp = rb_funcall(obj, NUM2INT(dbst->filter[type_kv]), 1, a);
	}
	else {
	    tmp = rb_funcall(dbst->filter[type_kv], bdb_id_call, 1, a);
	}
    }
    if (dbst->marshal) {
        if (rb_obj_is_kind_of(tmp, bdb_cDelegate)) {
	    tmp = bdb_deleg_to_orig(tmp);
        }
        tmp = rb_funcall(dbst->marshal, bdb_id_dump, 1, tmp);
        if (TYPE(tmp) != T_STRING) {
	    rb_raise(rb_eTypeError, "dump() must return String");
	}
    }
    else {
        tmp = rb_obj_as_string(tmp);
        if ((dbst->options & BDB_NIL) && a == Qnil) {
            is_nil = 1;
        }
    }
    key->data = StringValuePtr(tmp);
    key->flags &= ~DB_DBT_MALLOC;
    key->size = RSTRING(tmp)->len + is_nil;
    return tmp;
}

VALUE
bdb_test_ret(VALUE obj, VALUE tmp, VALUE a, int type_kv)
{
    bdb_DB *dbst;
    Data_Get_Struct(obj, bdb_DB, dbst);
    if (dbst->marshal || a == Qnil) {
	return a;
    }
    else {
        if (dbst->filter[type_kv]) {
            return rb_obj_as_string(a);
        }
        else {
	    return tmp;
	}
    }
}

VALUE
bdb_test_recno(VALUE obj, DBT *key, db_recno_t *recno, VALUE a)
{
    bdb_DB *dbst;
    Data_Get_Struct(obj, bdb_DB, dbst);
    if (RECNUM_TYPE(dbst)) {
        *recno = NUM2INT(a) + dbst->array_base;
        key->data = recno;
        key->size = sizeof(db_recno_t);
	return a;
    }
    else {
        return bdb_test_dump(obj, key, a, FILTER_KEY);
    }
}

VALUE
bdb_test_load(VALUE obj, DBT *a, int type_kv)
{
    VALUE res;
    int i, posi;
    bdb_DB *dbst;

    posi = type_kv & ~FILTER_FREE;
    Data_Get_Struct(obj, bdb_DB, dbst);
    if (dbst->marshal) {
	res = rb_str_new(a->data, a->size);
	if (dbst->filter[2 + posi]) {
	    if (FIXNUM_P(dbst->filter[2 + posi])) {
		res = rb_funcall(obj,
                                 NUM2INT(dbst->filter[2 + posi]), 1, res);
	    }
	    else {
		res = rb_funcall(dbst->filter[2 + posi], bdb_id_call, 1, res);
	    }
	}
	res = rb_funcall(dbst->marshal, bdb_id_load, 1, res);
    }
    else {
#if BDB_VERSION >= 30000
	if (dbst->type == DB_QUEUE) {
	    for (i = a->size - 1; i >= 0; i--) {
		if (((char *)a->data)[i] != dbst->re_pad)
		    break;
	    }
	    a->size = i + 1;
	}
#endif
	if ((dbst->options & BDB_NIL) &&
            a->size == 1 && ((char *)a->data)[0] == '\000') {
	    res = Qnil;
	}
	else {
            if (a->size == 0 && !(dbst->options & BDB_NIL)) {
                res = Qnil;
            }
            else {
                res = rb_tainted_str_new(a->data, a->size);
                if (dbst->filter[2 + posi]) {
                    if (FIXNUM_P(dbst->filter[2 + posi])) {
                        res = rb_funcall(obj, NUM2INT(dbst->filter[2 + posi]),
                                         1, res);
                    }
                    else {
                        res = rb_funcall(dbst->filter[2 + posi],
                                         bdb_id_call, 1, res);
                    }
                }
            }
	}
    }
    if ((a->flags & DB_DBT_MALLOC) && !(type_kv & FILTER_FREE)) {
	free(a->data);
        a->flags &= ~DB_DBT_MALLOC;
    }
    return res;
}

static VALUE
test_load_dyna1(VALUE obj, DBT *key, DBT *val)
{
    bdb_DB *dbst;
    VALUE del, res, tmp;
    struct deleg_class *delegst;
    
    Data_Get_Struct(obj, bdb_DB, dbst);
    res = bdb_test_load(obj, val, FILTER_VALUE);
    if (dbst->marshal && !SPECIAL_CONST_P(res)) {
	del = Data_Make_Struct(bdb_cDelegate, struct deleg_class, 
			       bdb_deleg_mark, free, delegst);
	delegst->db = obj;
	if (RECNUM_TYPE(dbst)) {
	    tmp = INT2NUM((*(db_recno_t *)key->data) - dbst->array_base);
	}
	else {
	    tmp = rb_str_new(key->data, key->size);
	    if (dbst->filter[2 + FILTER_VALUE]) {
		if (FIXNUM_P(dbst->filter[2 + FILTER_VALUE])) {
		    tmp = rb_funcall(obj, 
				     NUM2INT(dbst->filter[2 + FILTER_VALUE]),
				     1, tmp);
		}
		else {
		    tmp = rb_funcall(dbst->filter[2 + FILTER_VALUE],
				     bdb_id_call, 1, tmp);
		}
	    }
	    tmp = rb_funcall(dbst->marshal, bdb_id_load, 1, tmp);
	}
	delegst->key = tmp;
	delegst->obj = res;
	res = del;
    }
    return res;
}

static VALUE
test_load_dyna(VALUE obj, DBT *key, DBT *val)
{
    VALUE res = test_load_dyna1(obj, key, val);
    if (key->flags & DB_DBT_MALLOC) {
	free(key->data);
        key->flags &= ~DB_DBT_MALLOC;
    }
    return res;
}

static int
#if BDB_VERSION >= 30200
bdb_bt_compare(DB *dbbd, const DBT *a, const DBT *b)
#else
bdb_bt_compare(const DBT *a, const DBT *b)
#endif
{
    VALUE obj, av, bv, res;
    bdb_DB *dbst;

    GetIdDb(obj, dbst);
    av = bdb_test_load(obj, (DBT *)a, FILTER_VALUE|FILTER_FREE);
    bv = bdb_test_load(obj, (DBT *)b, FILTER_VALUE|FILTER_FREE);
    if (dbst->bt_compare == 0)
	res = rb_funcall(obj, id_bt_compare, 2, av, bv);
    else
	res = rb_funcall(dbst->bt_compare, bdb_id_call, 2, av, bv);
    return NUM2INT(res);
} 

static size_t
#if BDB_VERSION >= 30200
bdb_bt_prefix(DB *dbbd, const DBT *a, const DBT *b)
#else
bdb_bt_prefix(const DBT *a, const DBT *b)
#endif
{
    VALUE obj, av, bv, res;
    bdb_DB *dbst;

    GetIdDb(obj, dbst);
    av = bdb_test_load(obj, (DBT *)a, FILTER_VALUE|FILTER_FREE);
    bv = bdb_test_load(obj, (DBT *)b, FILTER_VALUE|FILTER_FREE);
    if (dbst->bt_prefix == 0)
	res = rb_funcall(obj, id_bt_prefix, 2, av, bv);
    else
	res = rb_funcall(dbst->bt_prefix, bdb_id_call, 2, av, bv);
    return NUM2INT(res);
} 

static int
#if BDB_VERSION >= 30200
bdb_dup_compare(DB *dbbd, const DBT *a, const DBT *b)
#else
bdb_dup_compare(const DBT *a, const DBT *b)
#endif
{
    VALUE obj, av, bv, res;
    bdb_DB *dbst;

    GetIdDb(obj, dbst);
    av = bdb_test_load(obj, (DBT *)a, FILTER_VALUE|FILTER_FREE);
    bv = bdb_test_load(obj, (DBT *)b, FILTER_VALUE|FILTER_FREE);
    if (dbst->dup_compare == 0)
	res = rb_funcall(obj, id_dup_compare, 2, av, bv);
    else
	res = rb_funcall(dbst->dup_compare, bdb_id_call, 2, av, bv);
    return NUM2INT(res);
}

static u_int32_t
#if BDB_VERSION >= 30200
bdb_h_hash(DB *dbbd, const void *bytes, u_int32_t length)
#else
bdb_h_hash(const void *bytes, u_int32_t length)
#endif
{
    VALUE obj, st, res;
    bdb_DB *dbst;

    GetIdDb(obj, dbst);
    st = rb_tainted_str_new((char *)bytes, length);
    if (dbst->h_hash == 0)
	res = rb_funcall(obj, id_h_hash, 1, st);
    else
	res = rb_funcall(dbst->h_hash, bdb_id_call, 1, st);
    return NUM2UINT(res);
}

#if BDB_VERSION >= 40100

static int
bdb_append_recno(DB *dbp, DBT *data, db_recno_t recno)
{
    VALUE res, obj, av, rec;
    bdb_DB *dbst;

    GetIdDb(obj, dbst);
    av = bdb_test_load(obj, data, FILTER_VALUE|FILTER_FREE);
    rec = INT2NUM(recno - dbst->array_base);
    if (dbst->append_recno == 0)
	res = rb_funcall(obj, id_append_recno, 2, rec, av);
    else
	res = rb_funcall(dbst->append_recno, bdb_id_call, 2, rec, av);
    if (!NIL_P(res)) {
	bdb_test_dump(obj, data, res, FILTER_VALUE);
    }
    return 0;
}

#endif

#if BDB_VERSION >= 30000

static void
bdb_feedback(DB *dbp, int opcode, int pct)
{
    VALUE obj;
    bdb_DB *dbst;

    GetIdDb(obj, dbst);
    if (NIL_P(dbst->feedback)) {
	return;
    }
    if (dbst->feedback == 0) {
	rb_funcall(obj, id_feedback, 2, INT2NUM(opcode), INT2NUM(pct));
    }
    else {
	rb_funcall(dbst->feedback, bdb_id_call, 2, INT2NUM(opcode), INT2NUM(pct));
    }
}

#endif

static VALUE
bdb_i_options(VALUE obj, VALUE dbstobj)
{
    VALUE key, value;
    char *options, *str;
    DB *dbp;
    bdb_DB *dbst;

    Data_Get_Struct(dbstobj, bdb_DB, dbst);
    key = rb_ary_entry(obj, 0);
    value = rb_ary_entry(obj, 1);
    dbp = dbst->dbp;
    key = rb_obj_as_string(key);
    options = StringValuePtr(key);
    if (strcmp(options, "set_bt_minkey") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->bt_minkey = NUM2INT(value);
#else
        bdb_test_error(dbp->set_bt_minkey(dbp, NUM2INT(value)));
#endif
    }
    else if (strcmp(options, "set_bt_compare") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->options |= BDB_BT_COMPARE;
	dbst->bt_compare = value;
#if BDB_VERSION < 30000
	dbst->dbinfo->bt_compare = bdb_bt_compare;
#else
	bdb_test_error(dbp->set_bt_compare(dbp, bdb_bt_compare));
#endif
    }
    else if (strcmp(options, "set_bt_prefix") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->options |= BDB_BT_PREFIX;
	dbst->bt_prefix = value;
#if BDB_VERSION < 30000
	dbst->dbinfo->bt_prefix = bdb_bt_prefix;
#else
	bdb_test_error(dbp->set_bt_prefix(dbp, bdb_bt_prefix));
#endif
    }
    else if (strcmp(options, "set_dup_compare") == 0) {
#ifdef DB_DUPSORT
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->options |= BDB_DUP_COMPARE;
	dbst->dup_compare = value;
#if BDB_VERSION < 30000
	dbst->dbinfo->dup_compare = bdb_dup_compare;
#else
	bdb_test_error(dbp->set_dup_compare(dbp, bdb_dup_compare));
#endif
#else
	rb_warning("dup_compare need db >= 2.5.9");
#endif
    }
    else if (strcmp(options, "set_h_hash") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->options |= BDB_H_HASH;
	dbst->h_hash = value;
#if BDB_VERSION < 30000
	dbst->dbinfo->h_hash = bdb_h_hash;
#else
	bdb_test_error(dbp->set_h_hash(dbp, bdb_h_hash));
#endif
    }
#if BDB_VERSION >= 40100
    else if (strcmp(options, "set_append_recno") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->options |= BDB_APPEND_RECNO;
	dbst->append_recno = value;
	bdb_test_error(dbp->set_append_recno(dbp, bdb_append_recno));
    }
#endif
    else if (strcmp(options, "set_cachesize") == 0) {
	switch (TYPE(value)) {
	case T_FIXNUM:
	case T_FLOAT:
	case T_BIGNUM:
#if BDB_VERSION < 30000
	    dbst->dbinfo->db_cachesize = NUM2INT(value);
#else
	    bdb_test_error(dbp->set_cachesize(dbp, 0, NUM2UINT(value), 0));
#endif
	    break;
	default:
	    Check_Type(value, T_ARRAY);
	    if (RARRAY(value)->len < 3) { 
		rb_raise(bdb_eFatal, "expected 3 values for cachesize");
	    }
#if BDB_VERSION < 30000
	    dbst->dbinfo->db_cachesize = NUM2INT(RARRAY(value)->ptr[1]);
#else
	    bdb_test_error(dbp->set_cachesize(dbp, 
					      NUM2INT(RARRAY(value)->ptr[0]),
					      NUM2INT(RARRAY(value)->ptr[1]),
					      NUM2INT(RARRAY(value)->ptr[2])));
#endif
	    break;
	}
    }
    else if (strcmp(options, "set_flags") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->flags = NUM2UINT(value);
#else
        bdb_test_error(dbp->set_flags(dbp, NUM2UINT(value)));
#endif
	dbst->flags |= NUM2UINT(value);
    }
    else if (strcmp(options, "set_h_ffactor") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->h_ffactor = NUM2INT(value);
#else
        bdb_test_error(dbp->set_h_ffactor(dbp, NUM2INT(value)));
#endif
    }
    else if (strcmp(options, "set_h_nelem") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->h_nelem = NUM2INT(value);
#else
        bdb_test_error(dbp->set_h_nelem(dbp, NUM2INT(value)));
#endif
    }
    else if (strcmp(options, "set_lorder") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->db_lorder = NUM2INT(value);
#else
        bdb_test_error(dbp->set_lorder(dbp, NUM2INT(value)));
#endif
    }
    else if (strcmp(options, "set_pagesize") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->db_pagesize = NUM2INT(value);
#else
        bdb_test_error(dbp->set_pagesize(dbp, NUM2INT(value)));
#endif
    }
    else if (strcmp(options, "set_re_delim") == 0) {
	int ch;
	if (TYPE(value) == T_STRING) {
	    str = StringValuePtr(value);
	    ch = str[0];
	}
	else {
	    ch = NUM2INT(value);
	}
#if BDB_VERSION < 30000
	dbst->dbinfo->re_delim = ch;
	dbst->dbinfo->flags |= DB_DELIMITER;
#else
        bdb_test_error(dbp->set_re_delim(dbp, ch));
#endif
    }
    else if (strcmp(options, "set_re_len") == 0) {
#if BDB_VERSION < 30000
	dbst->dbinfo->re_len = NUM2INT(value);
	dbst->dbinfo->flags |= DB_FIXEDLEN;
#else
        bdb_test_error(dbp->set_re_len(dbp, NUM2INT(value)));
#endif
    }
    else if (strcmp(options, "set_re_pad") == 0) {
	int ch;
	if (TYPE(value) == T_STRING) {
	    str = StringValuePtr(value);
	    ch = str[0];
	}
	else {
	    ch = NUM2INT(value);
	}
#if BDB_VERSION < 30000
	dbst->dbinfo->re_pad = ch;
	dbst->dbinfo->flags |= DB_PAD;
#else
        bdb_test_error(dbp->set_re_pad(dbp, ch));
#endif
    }
    else if (strcmp(options, "set_re_source") == 0) {
        if (TYPE(value) != T_STRING)
            rb_raise(bdb_eFatal, "re_source must be a filename");
#if BDB_VERSION < 30000
	dbst->dbinfo->re_source = StringValuePtr(value);
#else
        bdb_test_error(dbp->set_re_source(dbp, StringValuePtr(value)));
#endif
	dbst->options |= BDB_RE_SOURCE;
    }
#if BDB_VERSION >= 30200
    else if (strcmp(options, "set_q_extentsize") == 0) {
	bdb_test_error(dbp->set_q_extentsize(dbp, NUM2INT(value)));
    }
#endif
    else if (strcmp(options, "marshal") == 0) {
        switch (value) {
        case Qtrue: 
	    dbst->marshal = bdb_mMarshal; 
	    dbst->options |= BDB_MARSHAL;
	    break;
        case Qfalse: 
	    dbst->marshal = Qfalse; 
	    dbst->options &= ~BDB_MARSHAL;
	    break;
        default: 
	    if (!rb_respond_to(value, bdb_id_load) ||
		!rb_respond_to(value, bdb_id_dump)) {
		rb_raise(bdb_eFatal, "marshal value must be true or false");
	    }
	    dbst->marshal = value;
	    dbst->options |= BDB_MARSHAL;
	    break;
        }
    }
    else if (strcmp(options, "set_array_base") == 0 ||
	     strcmp(options, "array_base") == 0) {
	int ary_base = NUM2INT(value);
	switch (ary_base) {
	case 0: dbst->array_base = 1; break;
	case 1: dbst->array_base = 0; break;
	default: rb_raise(bdb_eFatal, "array base must be 0 or 1");
	}
    }
    else if (strcmp(options, "thread") == 0) {
	if (RTEST(value)) {
	    dbst->options &= ~BDB_NO_THREAD;
	}
	else {
	    dbst->options |= BDB_NO_THREAD;
	}
    }
    else if (strcmp(options, "set_store_key") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->filter[FILTER_KEY] = value;
    }
    else if (strcmp(options, "set_fetch_key") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->filter[2 + FILTER_KEY] = value;
    }
    else if (strcmp(options, "set_store_value") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->filter[FILTER_VALUE] = value;
    }
    else if (strcmp(options, "set_fetch_value") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->filter[2 + FILTER_VALUE] = value;
    }
#if BDB_VERSION >= 40100
    else if (strcmp(options, "set_encrypt") == 0) {
	char *passwd;
	int flags = DB_ENCRYPT_AES;
	if (TYPE(value) == T_ARRAY) {
	    if (RARRAY(value)->len != 2) {
		rb_raise(bdb_eFatal, "Expected an Array with 2 values");
	    }
	    passwd = StringValuePtr(RARRAY(value)->ptr[0]);
	    flags = NUM2INT(RARRAY(value)->ptr[1]);
	}
	else {
	    passwd = StringValuePtr(value);
	}
	bdb_test_error(dbp->set_encrypt(dbp, passwd, flags));
    }
#endif
#if BDB_VERSION >= 30000
    else if (strcmp(options, "set_feedback") == 0) {
	if (!rb_respond_to(value, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->options |= BDB_FEEDBACK;
	dbst->feedback = value;
	dbp->set_feedback(dbp, bdb_feedback);
    }
#endif
    else if (strcmp(options, "store_nil_as_null") == 0) {
        if (RTEST(value)) {
            dbst->options |= BDB_NIL;
        }
        else {
            dbst->options &= ~BDB_NIL;
        }
    }
    return Qnil;
}

struct bdb_eiv {
    VALUE bdb;
    bdb_DB *dbst;
};

static void
bdb_i_close(bdb_DB *dbst, int flags)
{
    bdb_ENV *envst;

    if (dbst->dbp) {
	if (RTEST(dbst->txn) && RBASIC(dbst->txn)->flags) {
	    bdb_TXN *txnst;
	    int opened = 0;

	    Data_Get_Struct(dbst->txn, bdb_TXN, txnst);
	    opened = bdb_ary_delete(&txnst->db_ary, dbst->ori_val);
	    if (!opened) {
		opened = bdb_ary_delete(&txnst->db_assoc, dbst->ori_val);
	    }
	    if (opened) {
		if (txnst->options & BDB_TXN_COMMIT) {
		    rb_funcall2(dbst->txn, rb_intern("commit"), 0, 0);
		}
		else {
		    rb_funcall2(dbst->txn, rb_intern("abort"), 0, 0);
		}
	    }
	}
	else {
	    if (dbst->env && RBASIC(dbst->env)->flags) {
		Data_Get_Struct(dbst->env, bdb_ENV, envst);
		bdb_ary_delete(&envst->db_ary, dbst->ori_val);
	    }
	    if (!(dbst->options & BDB_NOT_OPEN)) {
		bdb_test_error(dbst->dbp->close(dbst->dbp, flags));
	    }
	}
    }
    dbst->dbp = NULL;
}

VALUE
bdb_local_aref()
{
    bdb_DB *dbst;
    VALUE obj;

    GetIdDb(obj, dbst);
    return obj;
}

static VALUE
i_close(bdb_DB *dbst)
{
    bdb_i_close(dbst, 0);
    return Qnil;
}

static VALUE
bdb_final_aref(bdb_DB *dbst)
{
    VALUE obj, th;

    th = rb_thread_current();
    if (RTEST(th) && RBASIC(th)->flags) {
	obj = rb_thread_local_aref(th, bdb_id_current_db);
	if (!NIL_P(obj) && RDATA(obj)->dmark == (RUBY_DATA_FUNC)bdb_mark &&
	    DATA_PTR(obj) == dbst) {
	    rb_thread_local_aset(th, bdb_id_current_db, Qnil);
	}
    }
    return Qnil;
}

static void
bdb_free(bdb_DB *dbst)
{
    if (dbst->dbp && !(dbst->options & BDB_NOT_OPEN)) {
        rb_protect((VALUE (*)(ANYARGS))i_close, (VALUE)dbst, 0);
        rb_protect((VALUE (*)(ANYARGS))bdb_final_aref, (VALUE)dbst, 0);
    }
    free(dbst);
}

static void
bdb_mark(bdb_DB *dbst)
{
    int i;
    rb_gc_mark(dbst->marshal);
    rb_gc_mark(dbst->env);
    rb_gc_mark(dbst->txn);
    rb_gc_mark(dbst->orig);
    rb_gc_mark(dbst->secondary);
    rb_gc_mark(dbst->bt_compare);
    rb_gc_mark(dbst->bt_prefix);
    rb_gc_mark(dbst->dup_compare);
    for (i = 0; i < 4; i++) {
	rb_gc_mark(dbst->filter[i]);
    }
    rb_gc_mark(dbst->h_hash);
    rb_gc_mark(dbst->filename);
    rb_gc_mark(dbst->database);
#if BDB_VERSION >= 40100
    rb_gc_mark(dbst->append_recno);
#endif
#if BDB_VERSION >= 30000
    rb_gc_mark(dbst->feedback);
#endif
}

static VALUE
bdb_env(VALUE obj)
{
    bdb_DB *dbst;

    GetDB(obj, dbst);
    if (RTEST(dbst->env)) {
	return dbst->env;
    }
    return Qnil;
}

VALUE
bdb_env_p(VALUE obj)
{
    bdb_DB *dbst;

    GetDB(obj, dbst);
    return (RTEST(dbst->env)?Qtrue:Qfalse);
}

static VALUE
bdb_txn(VALUE obj)
{
    bdb_DB *dbst;

    GetDB(obj, dbst);
    if (RTEST(dbst->txn)) {
	return dbst->txn;
    }
    return Qnil;
}

VALUE
bdb_txn_p(VALUE obj)
{
    bdb_DB *dbst;

    GetDB(obj, dbst);
    return (RTEST(dbst->txn)?Qtrue:Qfalse);
}

static VALUE
bdb_close(int argc, VALUE *argv, VALUE obj)
{
    VALUE opt;
    bdb_DB *dbst;
    int flags = 0;

    if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4) {
	rb_raise(rb_eSecurityError, "Insecure: can't close the database");
    }
    Data_Get_Struct(obj, bdb_DB, dbst);
    if (dbst->dbp != NULL) {
	if (rb_scan_args(argc, argv, "01", &opt)) {
	    flags = NUM2INT(opt);
	}
	bdb_i_close(dbst, flags);
    }
    dbst->options |= BDB_NOT_OPEN;
    rb_protect((VALUE (*)(ANYARGS))bdb_final_aref, (VALUE)dbst, 0);
    RDATA(obj)->dfree = free;
    return Qnil;
}

#if BDB_VERSION < 30100
static long
bdb_hard_count(dbp)
    DB *dbp;
{
    DBC *dbcp;
    DBT key, data;
    db_recno_t recno;
    long count = 0;
    int ret;

    MEMZERO(&key, DBT, 1);
    key.data = &recno;
    key.size = sizeof(db_recno_t);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
#if BDB_VERSION < 20600
    key.flags |= DB_DBT_MALLOC;
    bdb_test_error(dbp->cursor(dbp, 0, &dbcp));
#else
    bdb_test_error(dbp->cursor(dbp, 0, &dbcp, 0));
#endif
    do {
        bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_NEXT),
			dbcp->c_close(dbcp), ret);
        if (ret == DB_NOTFOUND) {
            dbcp->c_close(dbcp);
            return count;
        }
	if (ret == DB_KEYEMPTY)  {
            dbcp->c_close(dbcp);
	    return -1;
	}
	free(data.data);
        count++;
    } while (1);
    return count;
}
#endif

static long
bdb_is_recnum(dbp)
    DB *dbp;
{
    DB_BTREE_STAT *bdb_stat;
    long count;
#if BDB_VERSION < 30100
    long hard;
#endif

#if BDB_VERSION >= 30100
#if BDB_VERSION >= 30300
#if BDB_VERSION >= 40300
    bdb_test_error(dbp->stat(dbp, NULL, &bdb_stat, 0));
#else
    bdb_test_error(dbp->stat(dbp, &bdb_stat, 0));
#endif
#else
    bdb_test_error(dbp->stat(dbp, &bdb_stat, 0, 0));
#endif
    count = (bdb_stat->bt_nkeys == bdb_stat->bt_ndata)?bdb_stat->bt_nkeys:-1;
    free(bdb_stat);
#else
    bdb_test_error(dbp->stat(dbp, &bdb_stat, 0, DB_RECORDCOUNT));
    count = bdb_stat->bt_nrecs;
    free(bdb_stat);
    hard = bdb_hard_count(dbp);
    count = (count == hard)?count:-1;
#endif
    return count;
}

static VALUE
bdb_recno_length(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    DB_BTREE_STAT *bdb_stat;
    VALUE hash;
#if BDB_VERSION >= 40300
    DB_TXN *txnid = NULL;
#endif

    GetDB(obj, dbst);
#if BDB_VERSION >= 40000
#if BDB_VERSION >= 40300
    if (RTEST(dbst->txn)) {
        bdb_TXN *txnst;

        GetTxnDB(dbst->txn, txnst);
        txnid = txnst->txnid;
    }
    bdb_test_error(dbst->dbp->stat(dbst->dbp, txnid, &bdb_stat, DB_FAST_STAT));
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, DB_FAST_STAT));
#endif
#else
#if BDB_VERSION >= 30300
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, DB_RECORDCOUNT));
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, 0, DB_RECORDCOUNT));
#endif
#endif
#if BDB_VERSION >= 30100
    hash = INT2NUM(bdb_stat->bt_nkeys);
#else
    hash = INT2NUM(bdb_stat->bt_nrecs);
#endif
    free(bdb_stat);
    return hash;
}

static VALUE
bdb_s_new(int argc, VALUE *argv, VALUE obj)
{
    VALUE res;
    bdb_TXN *txnst = NULL;
    bdb_ENV *envst = NULL;
    bdb_DB *dbst;
    DB_ENV *envp = 0;

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    res = rb_obj_alloc(obj);
#else
    res = rb_funcall2(obj, rb_intern("allocate"), 0, 0);
#endif
    Data_Get_Struct(res, bdb_DB, dbst);
    if (argc && TYPE(argv[argc - 1]) == T_HASH) {
	VALUE v, f = argv[argc - 1];

	if ((v = rb_hash_aref(f, rb_str_new2("txn"))) != RHASH(f)->ifnone) {
	    if (!rb_obj_is_kind_of(v, bdb_cTxn)) {
		rb_raise(bdb_eFatal, "argument of txn must be a transaction");
	    }
	    Data_Get_Struct(v, bdb_TXN, txnst);
	    dbst->txn = v;
	    dbst->env = txnst->env;
	    Data_Get_Struct(txnst->env, bdb_ENV, envst);
	    envp = envst->envp;
	    dbst->options |= envst->options & BDB_NO_THREAD;
	    dbst->marshal = txnst->marshal;
	}
	else if ((v = rb_hash_aref(f, rb_str_new2("env"))) != RHASH(f)->ifnone) {
	    if (!rb_obj_is_kind_of(v, bdb_cEnv)) {
		rb_raise(bdb_eFatal, "argument of env must be an environnement");
	    }
	    Data_Get_Struct(v, bdb_ENV, envst);
	    dbst->env = v;
	    envp = envst->envp;
	    dbst->options |= envst->options & BDB_NO_THREAD;
	    dbst->marshal = envst->marshal;
	}
#ifdef DB_ENCRYPT 
	if (envst && (envst->options & BDB_ENV_ENCRYPT)) {
	    VALUE tmp = rb_str_new2("set_flags");
	    if ((v = rb_hash_aref(f, rb_intern("set_flags"))) != RHASH(f)->ifnone) {
		rb_hash_aset(f, rb_intern("set_flags"), 
			     INT2NUM(NUM2INT(v) | DB_ENCRYPT));
	    }
	    else if ((v = rb_hash_aref(f, tmp)) != RHASH(f)->ifnone) {
		rb_hash_aset(f, tmp, INT2NUM(NUM2INT(v) | DB_ENCRYPT));
	    }
	    else {
		rb_hash_aset(f, tmp, INT2NUM(DB_ENCRYPT));
	    }
	}
#endif
    }
#if BDB_VERSION >= 30000
    bdb_test_error(db_create(&(dbst->dbp), envp, 0));
    dbst->dbp->set_errpfx(dbst->dbp, "BDB::");
    dbst->dbp->set_errcall(dbst->dbp, bdb_env_errcall);
#endif
    if (rb_respond_to(obj, bdb_id_load) == Qtrue &&
	rb_respond_to(obj, bdb_id_dump) == Qtrue) {
	dbst->marshal = obj;
	dbst->options |= BDB_MARSHAL;
    }
    if (rb_method_boundp(obj, rb_intern("bdb_store_key"), 0) == Qtrue) {
	dbst->filter[FILTER_KEY] = INT2FIX(rb_intern("bdb_store_key"));
    }
    if (rb_method_boundp(obj, rb_intern("bdb_fetch_key"), 0) == Qtrue) {
	dbst->filter[2 + FILTER_KEY] = INT2FIX(rb_intern("bdb_fetch_key"));
    }
    if (rb_method_boundp(obj, rb_intern("bdb_store_value"), 0) == Qtrue) {
	dbst->filter[FILTER_VALUE] = INT2FIX(rb_intern("bdb_store_value"));
    }
    if (rb_method_boundp(obj, rb_intern("bdb_fetch_value"), 0) == Qtrue) {
	dbst->filter[2 + FILTER_VALUE] = INT2FIX(rb_intern("bdb_fetch_value"));
    }
    rb_obj_call_init(res, argc, argv);
    if (txnst) {
	bdb_ary_push(&txnst->db_ary, res);
    }
    else if (envst) {
	bdb_ary_push(&envst->db_ary, res);
    }
    return res;
}

VALUE
bdb_init(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    DB *dbp;
    int flags, mode, ret, nb;
    char *name, *subname;
    VALUE a, b, d, e;
    VALUE hash_arg = Qnil;
#if BDB_VERSION < 30000
    DB_INFO dbinfo;

    MEMZERO(&dbinfo, DB_INFO, 1);
#endif
    Data_Get_Struct(obj, bdb_DB, dbst);
    dbp = dbst->dbp;
#if BDB_VERSION < 30000
    dbst->dbinfo = &dbinfo;
#endif
#if BDB_VERSION >= 40100
    if (rb_const_defined(CLASS_OF(obj), rb_intern("BDB_ENCRYPT"))) {
	char *passwd;
	int flags = DB_ENCRYPT_AES;
	VALUE value = rb_const_get(CLASS_OF(obj), rb_intern("BDB_ENCRYPT"));
	if (TYPE(value) == T_ARRAY) {
	    if (RARRAY(value)->len != 2) {
		rb_raise(bdb_eFatal, "Expected an Array with 2 values");
	    }
	    passwd = StringValuePtr(RARRAY(value)->ptr[0]);
	    flags = NUM2INT(RARRAY(value)->ptr[1]);
	}
	else {
	    passwd = StringValuePtr(value);
	}
	bdb_test_error(dbp->set_encrypt(dbp, passwd, flags));
    }
#endif
    if (argc && TYPE(argv[argc - 1]) == T_HASH) {
	hash_arg = argv[argc - 1];
	rb_iterate(rb_each, argv[argc - 1], bdb_i_options, obj);
        argc--;
    }
    mode = flags = 0;
    if (argc) {
	flags = DB_RDONLY;
    }
    a = b = d = e = Qnil;
    switch(nb = rb_scan_args(argc, argv, "04", &a, &b, &d, &e)) {
    case 4:
	mode = NUM2INT(e);
    case 3:
	if (TYPE(d) == T_STRING) {
	    if (strcmp(StringValuePtr(d), "r") == 0)
		flags = DB_RDONLY;
	    else if (strcmp(StringValuePtr(d), "r+") == 0)
		flags = 0;
	    else if (strcmp(StringValuePtr(d), "w") == 0 ||
		     strcmp(StringValuePtr(d), "w+") == 0)
		flags = DB_CREATE | DB_TRUNCATE;
	    else if (strcmp(StringValuePtr(d), "a") == 0 ||
		     strcmp(StringValuePtr(d), "a+") == 0)
		flags = DB_CREATE;
	    else {
		rb_raise(bdb_eFatal, "flags must be r, r+, w, w+, a or a+");
	    }
	}
	else if (d == Qnil)
	    flags = DB_RDONLY;
	else
	    flags = NUM2INT(d);
    }
    name = subname = NULL;
    if (!NIL_P(a)) {
	SafeStringValue(a);
        name = StringValuePtr(a);
    }
    if (!NIL_P(b)) {
	SafeStringValue(b);
        subname = StringValuePtr(b);
    }
    if (dbst->bt_compare == 0 && rb_respond_to(obj, id_bt_compare) == Qtrue) {
	dbst->options |= BDB_BT_COMPARE;
#if BDB_VERSION < 30000
	dbst->dbinfo->bt_compare = bdb_bt_compare;
#else
	bdb_test_error(dbp->set_bt_compare(dbp, bdb_bt_compare));
#endif
    }
    if (dbst->bt_prefix == 0 && rb_respond_to(obj, id_bt_prefix) == Qtrue) {
	dbst->options |= BDB_BT_PREFIX;
#if BDB_VERSION < 30000
	dbst->dbinfo->bt_prefix = bdb_bt_prefix;
#else
	bdb_test_error(dbp->set_bt_prefix(dbp, bdb_bt_prefix));
#endif
    }
#ifdef DB_DUPSORT
    if (dbst->dup_compare == 0 && rb_respond_to(obj, id_dup_compare) == Qtrue) {
	dbst->options |= BDB_DUP_COMPARE;
#if BDB_VERSION < 30000
	dbst->dbinfo->dup_compare = bdb_dup_compare;
#else
	bdb_test_error(dbp->set_dup_compare(dbp, bdb_dup_compare));
#endif
    }
#endif
    if (dbst->h_hash == 0 && rb_respond_to(obj, id_h_hash) == Qtrue) {
	dbst->options |= BDB_H_HASH;
#if BDB_VERSION < 30000
	dbst->dbinfo->h_hash = bdb_h_hash;
#else
	bdb_test_error(dbp->set_h_hash(dbp, bdb_h_hash));
#endif
    }
#if BDB_VERSION >= 40100
    if (dbst->append_recno == 0 && rb_respond_to(obj, id_append_recno) == Qtrue) {
	dbst->options |= BDB_APPEND_RECNO;
	bdb_test_error(dbp->set_append_recno(dbp, bdb_append_recno));
    }
#endif
#if BDB_VERSION >= 30000
    if (dbst->feedback == 0 && rb_respond_to(obj, id_feedback) == Qtrue) {
	dbp->set_feedback(dbp, bdb_feedback);
	dbst->options |= BDB_FEEDBACK;
    }
#endif
    if (flags & DB_TRUNCATE) {
	rb_secure(2);
    }
    if (flags & DB_CREATE) {
	rb_secure(4);
    }
    if (rb_safe_level() >= 4) {
	flags |= DB_RDONLY;
    }
#ifdef DB_DUPSORT
    if (dbst->options & BDB_DUP_COMPARE) {
#if BDB_VERSION < 30000
	dbst->dbinfo->flags = DB_DUP | DB_DUPSORT;
#else
        bdb_test_error(dbp->set_flags(dbp, DB_DUP | DB_DUPSORT));
#endif
    }
#endif
#ifndef BDB_NO_THREAD_COMPILE
    if (!(dbst->options & (BDB_RE_SOURCE | BDB_NO_THREAD))) {
	flags |= DB_THREAD;
    }
#endif
    if (dbst->options & BDB_NEED_CURRENT) {
	rb_thread_local_aset(rb_thread_current(), bdb_id_current_db, obj);
    }

#if BDB_VERSION < 30000
    {
	bdb_ENV *envst;
	DB_ENV *envp = NULL;
	if (dbst->env) {
	    Data_Get_Struct(dbst->env, bdb_ENV, envst);
	    envp = envst->envp;
	}

	if (name == NULL && (flags & DB_RDONLY)) {
	    flags &= ~DB_RDONLY;
	}
	    
	if ((ret = db_open(name, dbst->type, flags, mode, envp,
			   dbst->dbinfo, &dbp)) != 0) {
	    if (bdb_errcall) {
		bdb_errcall = 0;
		rb_raise(bdb_eFatal, "%s -- %s", StringValuePtr(bdb_errstr), db_strerror(ret));
	    }
	    else
		rb_raise(bdb_eFatal, "%s", db_strerror(ret));
	}
	dbst->dbp = dbp;
    }
#else
    {
	DB_TXN *txnid = NULL;

	if (name == NULL && subname == NULL) {
	    if (flags & DB_RDONLY) {
		flags &= ~DB_RDONLY;
	    }
#if BDB_VERSION >= 40416
	    flags |= DB_CREATE;
#endif
	}
#if BDB_VERSION >= 40100
	if (RTEST(dbst->txn)) {
	    bdb_TXN *txnst;

	    GetTxnDB(dbst->txn, txnst);
	    txnid = txnst->txnid;
	}
	else if (RTEST(dbst->env)) {
	    bdb_ENV *envst;

	    GetEnvDB(dbst->env, envst);
	    if (envst->options & BDB_AUTO_COMMIT) {
		flags |= DB_AUTO_COMMIT;
		dbst->options |= BDB_AUTO_COMMIT;
	    }
	}
	ret = dbp->open(dbp, txnid, name, subname, dbst->type, flags, mode);
#else
	ret = dbp->open(dbp, name, subname, dbst->type, flags, mode);
#endif
	if (ret != 0) {
	    dbp->close(dbp, 0);
	    if (bdb_errcall) {
		bdb_errcall = 0;
		rb_raise(bdb_eFatal, "%s -- %s", StringValuePtr(bdb_errstr), db_strerror(ret));
	    }
	    else {
		rb_raise(bdb_eFatal, "%s", db_strerror(ret));
	    }
	}
    }
#endif
    dbst->options &= ~BDB_NOT_OPEN;
    if (dbst->env) {
	bdb_ENV *envst;
	Data_Get_Struct(dbst->env, bdb_ENV, envst);
	dbst->options |= envst->options & BDB_INIT_LOCK;
    }
    dbst->filename = dbst->database = Qnil;
    if (name) {
	dbst->filename = rb_tainted_str_new2(name);
	OBJ_FREEZE(dbst->filename);
    }
#if BDB_VERSION >= 30000
    if (subname) {
	dbst->database = rb_tainted_str_new2(subname);
	OBJ_FREEZE(dbst->database);
    }
#endif
    dbst->len = -2;
    if (dbst->type == DB_UNKNOWN) {
#if BDB_VERSION < 30000
	dbst->type = dbst->dbp->type;
#else
#if BDB_VERSION >= 30300
	{
	    DBTYPE new_type;
	    bdb_test_error(dbst->dbp->get_type(dbst->dbp, &new_type));
	    dbst->type = (int)new_type;
	}
#else
	dbst->type = dbst->dbp->get_type(dbst->dbp);
#endif
#endif
	switch(dbst->type) {
	case DB_BTREE:
	    RBASIC(obj)->klass = bdb_cBtree;
	    break;
	case DB_HASH:
	    RBASIC(obj)->klass = bdb_cHash;
	    break;
	case DB_RECNO:
	{
	    long count;

	    rb_warning("It's hard to distinguish Recnum with Recno for all versions of Berkeley DB");
	    if ((count = bdb_is_recnum(dbst->dbp)) != -1) {
		RBASIC(obj)->klass = bdb_cRecnum;
		dbst->len = count;
	    }
	    else {
		RBASIC(obj)->klass = bdb_cRecno;
	    }
	    break;
	}
#if BDB_VERSION >= 30000
	case DB_QUEUE:
	    RBASIC(obj)->klass = bdb_cQueue;
	    break;
#endif
	default:
	    dbst->dbp->close(dbst->dbp, 0);
            dbst->dbp = NULL;
	    rb_raise(bdb_eFatal, "Unknown DB type");
	}
    }
    if (dbst->len == -2 && rb_obj_is_kind_of(obj, bdb_cRecnum)) {
	long count;

	if ((count = bdb_is_recnum(dbst->dbp)) != -1) {
	    VALUE len = bdb_recno_length(obj);
	    dbst->len = NUM2LONG(len);
	}
	else {
	    if (flags & DB_TRUNCATE) {
		dbst->len = 0;
	    }
	    else {
                dbst->dbp->close(dbst->dbp, 0);
                dbst->dbp = NULL;
		rb_raise(bdb_eFatal, "database is not a Recnum");
	    }
	}
    }
    return obj;
}

static VALUE
bdb_s_alloc(obj)
    VALUE obj;
{
    VALUE res, cl;
    bdb_DB *dbst;

    res = Data_Make_Struct(obj, bdb_DB, bdb_mark, bdb_free, dbst);
    dbst->options = BDB_NOT_OPEN;
    cl = obj;
    while (cl) {
	if (cl == bdb_cBtree || RCLASS(cl)->m_tbl == RCLASS(bdb_cBtree)->m_tbl) {
	    dbst->type = DB_BTREE; 
	    break;
	}
	if (cl == bdb_cRecnum || RCLASS(cl)->m_tbl == RCLASS(bdb_cRecnum)->m_tbl) {
	    dbst->type = DB_RECNO; 
	    break;
	}
	else if (cl == bdb_cHash || RCLASS(cl)->m_tbl == RCLASS(bdb_cHash)->m_tbl) {
	    dbst->type = DB_HASH; 
	    break;
	}
	else if (cl == bdb_cRecno || RCLASS(cl)->m_tbl == RCLASS(bdb_cRecno)->m_tbl) {
	    dbst->type = DB_RECNO;
	    break;
    }
#if BDB_VERSION >= 30000
	else if (cl == bdb_cQueue || RCLASS(cl)->m_tbl == RCLASS(bdb_cQueue)->m_tbl) {
	    dbst->type = DB_QUEUE;
	    break;
	}
#endif
	else if (cl == bdb_cUnknown || RCLASS(cl)->m_tbl == RCLASS(bdb_cUnknown)->m_tbl) {
	    dbst->type = DB_UNKNOWN;
	    break;
	}
	cl = RCLASS(cl)->super;
    }
    if (!cl) {
	rb_raise(bdb_eFatal, "unknown database type");
    }
    dbst->ori_val = res;
    return res;
}

static VALUE
bdb_i_s_create(obj, db)
    VALUE obj, db;
{
    VALUE tmp[2];
    tmp[0] = rb_ary_entry(obj, 0);
    tmp[1] = rb_ary_entry(obj, 1);
    bdb_put(2, tmp, db);
    return Qnil;
} 

static VALUE
bdb_s_create(int argc, VALUE *argv, VALUE obj)
{
    VALUE res;
    int i;

    res = rb_funcall2(obj, rb_intern("new"), 0, 0);
    if (argc == 1 && TYPE(argv[0]) == T_HASH) {
	rb_iterate(rb_each, argv[0], bdb_i_s_create, res);
	return res;
    }
    if (argc % 2 != 0) {
        rb_raise(rb_eArgError, "odd number args for %s", rb_class2name(obj));
    }
    for (i = 0; i < argc; i += 2) {
        bdb_put(2, argv + i, res);
    }
    return res;
}

static VALUE
bdb_s_open(int argc, VALUE *argv, VALUE obj)
{
    VALUE res = rb_funcall2(obj, rb_intern("new"), argc, argv);
    if (rb_block_given_p()) {
	return rb_ensure(rb_yield, res, bdb_protect_close, res);
    }
    return res;
}

#if BDB_VERSION >= 30000

struct re {
    int re_len, re_pad;
};

static VALUE
bdb_queue_i_search_re_len(obj, restobj)
    VALUE obj, restobj;
{
    VALUE key, value;
    char *str;
    struct re *rest;

    Data_Get_Struct(restobj, struct re, rest);
    key = rb_ary_entry(obj, 0);
    value = rb_ary_entry(obj, 1);
    key = rb_obj_as_string(key);
    if (strcmp(StringValuePtr(key), "set_re_len") == 0) {
	rest->re_len = NUM2INT(value);
    }
    else if (strcmp(StringValuePtr(key), "set_re_pad") == 0) {
	int ch;
	if (TYPE(value) == T_STRING) {
	    str = StringValuePtr(value);
	    ch = str[0];
	}
	else {
	    ch = NUM2INT(value);
	}
	rest->re_pad = ch;
    }
    return Qnil;
}

#define DEFAULT_RECORD_LENGTH 132
#define DEFAULT_RECORD_PAD 0x20

static VALUE
bdb_queue_s_new(int argc, VALUE *argv, VALUE obj) 
{
    VALUE *nargv, ret, restobj;
    struct re *rest;
    bdb_DB *dbst;

    restobj = Data_Make_Struct(obj, struct re, 0, free, rest);
    rest->re_len = -1;
    rest->re_pad = -1;
    if (argc && TYPE(argv[argc - 1]) == T_HASH) {
	rb_iterate(rb_each, argv[argc - 1], bdb_queue_i_search_re_len, restobj);
	if (rest->re_len <= 0) {
	    rest->re_len = DEFAULT_RECORD_LENGTH;
	    rb_hash_aset(argv[argc - 1], rb_tainted_str_new2("set_re_len"), INT2NUM(rest->re_len));
	}
	if (rest->re_pad < 0) {
	    rest->re_pad = DEFAULT_RECORD_PAD;
	    rb_hash_aset(argv[argc - 1], rb_tainted_str_new2("set_re_pad"), INT2NUM(rest->re_pad));
	}
	nargv = argv;
    }
    else {
	nargv = ALLOCA_N(VALUE, argc + 1);
	MEMCPY(nargv, argv, VALUE, argc);
	nargv[argc] = rb_hash_new();
	rest->re_len = DEFAULT_RECORD_LENGTH;
	rest->re_pad = DEFAULT_RECORD_PAD;
	rb_hash_aset(nargv[argc], rb_tainted_str_new2("set_re_len"), INT2NUM(DEFAULT_RECORD_LENGTH));
	rb_hash_aset(nargv[argc], rb_tainted_str_new2("set_re_pad"), INT2NUM(DEFAULT_RECORD_PAD));
	argc += 1;
    }
    ret = bdb_s_new(argc, nargv, obj);
    Data_Get_Struct(ret, bdb_DB, dbst);
    dbst->re_len = rest->re_len;
    dbst->re_pad = rest->re_pad;
    return ret;
}
#endif

static VALUE
bdb_append_internal(argc, argv, obj, flag, retval)
    int argc, flag;
    VALUE *argv, obj;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT key, data;
    db_recno_t recno;
    int i;
    VALUE *a, ary = Qnil;
    volatile VALUE res = Qnil;

    rb_secure(4);
    if (argc < 1)
	return obj;
    INIT_TXN(txnid, obj, dbst);
    if (txnid == NULL && (dbst->options & BDB_AUTO_COMMIT)) {
      flag |= DB_AUTO_COMMIT;
    }
    MEMZERO(&key, DBT, 1);
    recno = 1;
    key.data = &recno;
    key.size = sizeof(db_recno_t);
#if BDB_VERSION >= 40100
    if (flag & DB_APPEND) {
	key.flags |= DB_DBT_MALLOC;
    }
#endif
    if (retval) {
	ary = rb_ary_new();
    }
    for (i = 0, a = argv; i < argc; i++, a++) {
	MEMZERO(&data, DBT, 1);
	res = bdb_test_dump(obj, &data, *a, FILTER_VALUE);
	SET_PARTIAL(dbst, data);
#if BDB_VERSION >= 30000
	if (dbst->type == DB_QUEUE && dbst->re_len < data.size) {
	    rb_raise(bdb_eFatal, "size > re_len for Queue");
	}
#endif
	bdb_test_error(dbst->dbp->put(dbst->dbp, txnid, &key, &data, flag));
	if (retval) {
	    rb_ary_push(ary, INT2NUM(*(db_recno_t *)key.data));
	}
    }
    if (retval) {
	return ary;
    }
    return obj;
}

static VALUE
bdb_append_m(int argc, VALUE *argv, VALUE obj)
{
    return bdb_append_internal(argc, argv, obj, DB_APPEND, Qtrue);
}

static VALUE
bdb_append(obj, val)
    VALUE val, obj;
{
    return bdb_append_internal(1, &val, obj, DB_APPEND, Qfalse);
}

static VALUE
bdb_unshift(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    int flag;

    INIT_TXN(txnid, obj, dbst);
    if (dbst->flags & DB_RENUMBER) {
	flag = 0;
    }
    else {
	flag = DB_NOOVERWRITE;
    }
    return bdb_append_internal(argc, argv, obj, flag, Qtrue);
}

VALUE
bdb_put(int argc, VALUE *argv, VALUE obj)
{
    volatile VALUE a0 = Qnil;
    volatile VALUE b0 = Qnil;
    VALUE a, b, c;
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT key, data;
    int ret, flags;
    db_recno_t recno;

    rb_secure(4);
    INIT_TXN(txnid, obj, dbst);
    flags = 0;
    a = b = c = Qnil;
    MEMZERO(&key, DBT, 1);
    MEMZERO(&data, DBT, 1);
    if (rb_scan_args(argc, argv, "21", &a, &b, &c) == 3) {
        flags = NUM2INT(c);
    }
    a0 = bdb_test_recno(obj, &key, &recno, a);
    b0 = bdb_test_dump(obj, &data, b, FILTER_VALUE);
    SET_PARTIAL(dbst, data);
#if BDB_VERSION >= 30000
    if (dbst->type == DB_QUEUE && dbst->re_len < data.size) {
	rb_raise(bdb_eFatal, "size > re_len for Queue");
    }
#endif
    if (txnid == NULL && (dbst->options & BDB_AUTO_COMMIT)) {
      flags |= DB_AUTO_COMMIT;
    }
    ret = bdb_test_error(dbst->dbp->put(dbst->dbp, txnid, &key, &data, flags));
    if (ret == DB_KEYEXIST)
	return Qfalse;
    else {
	if (dbst->partial) {
	    if (flags & DB_APPEND) {
		a = INT2NUM((long)key.data);
	    }
	    return bdb_get(1, &a, obj);
	}
	else {
	    return bdb_test_ret(obj, b0, b, FILTER_VALUE);
	}
    }
}

static VALUE
bdb_aset(obj, a, b)
    VALUE obj, a, b;
{
    VALUE tmp[2];
    tmp[0] = a;
    tmp[1] = b;
    bdb_put(2, tmp, obj);
    return b;
}

VALUE
bdb_test_load_key(obj, key)
    VALUE obj;
    DBT *key;
{
    bdb_DB *dbst;
    Data_Get_Struct(obj, bdb_DB, dbst);
    if (RECNUM_TYPE(dbst))
        return INT2NUM((*(db_recno_t *)key->data) - dbst->array_base);
    return bdb_test_load(obj, key, FILTER_KEY);
}

VALUE
bdb_assoc(obj, key, data)
    VALUE obj;
    DBT *key, *data;
{
    return rb_assoc_new(bdb_test_load_key(obj, key), 
			bdb_test_load(obj, data, FILTER_VALUE));
}

VALUE
bdb_assoc_dyna(obj, key, data)
    VALUE obj;
    DBT *key, *data;
{
    VALUE k, v;
    int to_free = key->flags & DB_DBT_MALLOC;

    key->flags &= ~DB_DBT_MALLOC;
    k = bdb_test_load_key(obj, key);
    v = test_load_dyna1(obj, key, data);
    if (to_free) {
        free(key->data);
    }
    return rb_assoc_new(k, v);
}

static VALUE
bdb_assoc2(obj, skey, pkey, data)
    VALUE obj;
    DBT *skey, *pkey, *data;
{
    return rb_assoc_new(
	rb_assoc_new(bdb_test_load_key(obj, skey), bdb_test_load_key(obj, pkey)),
	bdb_test_load(obj, data, FILTER_VALUE));
}

VALUE
bdb_assoc3(obj, skey, pkey, data)
    VALUE obj;
    DBT *skey, *pkey, *data;
{
    return rb_ary_new3(3, bdb_test_load_key(obj, skey), 
		       bdb_test_load_key(obj, pkey),
		       bdb_test_load(obj, data, FILTER_VALUE));
}

static VALUE bdb_has_both _((VALUE, VALUE, VALUE));
static VALUE bdb_has_both_internal _((VALUE, VALUE, VALUE, VALUE));

static VALUE
bdb_get_internal(argc, argv, obj, notfound, dyna)
    int argc;
    VALUE *argv;
    VALUE obj;
    VALUE notfound;
    int dyna;
{
    VALUE a = Qnil;
    VALUE b = Qnil;
    VALUE c;
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT key, data;
#if BDB_VERSION < 20600
#define DB_GET_BOTH 9
#endif
    int flagss;
    int ret, flags;
    db_recno_t recno;

    INIT_TXN(txnid, obj, dbst);
    flagss = flags = 0;
    MEMZERO(&key, DBT, 1);
    MEMZERO(&data, DBT, 1);
    data.flags |= DB_DBT_MALLOC;
    switch(rb_scan_args(argc, argv, "12", &a, &b, &c)) {
    case 3:
        flags = NUM2INT(c);
        if ((flags & ~DB_RMW) == DB_GET_BOTH) {
#if BDB_VERSION < 20600
	    return bdb_has_both_internal(obj, a, b, Qtrue);
#else
            b = bdb_test_dump(obj, &data, b, FILTER_VALUE);
	    data.flags |= DB_DBT_MALLOC;
#endif
        }
	break;
    case 2:
	flagss = flags = NUM2INT(b);
	break;
    }
    a = bdb_test_recno(obj, &key, &recno, a);
    SET_PARTIAL(dbst, data);
    flags |= TEST_INIT_LOCK(dbst);
    ret = bdb_test_error(dbst->dbp->get(dbst->dbp, txnid, &key, &data, flags));
    if (ret == DB_NOTFOUND || ret == DB_KEYEMPTY)
        return notfound;
    if (((flags & ~DB_RMW) == DB_GET_BOTH) ||
	((flags & ~DB_RMW) == DB_SET_RECNO)) {
        return bdb_assoc(obj, &key, &data);
    }
    if (dyna) {
	return test_load_dyna(obj, &key, &data);
    }
    else {
	return bdb_test_load(obj, &data, FILTER_VALUE);
    }
}

VALUE
bdb_get(int argc, VALUE *argv, VALUE obj)
{
    return bdb_get_internal(argc, argv, obj, Qnil, 0);
}

static VALUE
bdb_get_dyna(int argc, VALUE *argv, VALUE obj)
{
    return bdb_get_internal(argc, argv, obj, Qnil, 1);
}

static VALUE
bdb_fetch(int argc, VALUE *argv, VALUE obj)
{
    VALUE key, if_none;
    VALUE val;

    rb_scan_args(argc, argv, "11", &key, &if_none);
    val = bdb_get_internal(1, argv, obj, Qundef, 1);
    if (val == Qundef) {
	if (rb_block_given_p()) {
	    if (argc > 1) {
		rb_raise(rb_eArgError, "wrong # of arguments");
	    }
	    return rb_yield(key);
	}
	if (argc == 1) {
	    rb_raise(rb_eIndexError, "key not found");
	}
	return if_none;
    }
    return val;
}

#if BDB_VERSION >= 30300

static VALUE
bdb_pget(int argc, VALUE *argv, VALUE obj)
{
    VALUE a = Qnil;
    VALUE b = Qnil;
    VALUE c;
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT pkey, data, skey;
    int flagss;
    int ret, flags;
    db_recno_t srecno;

    INIT_TXN(txnid, obj, dbst);
    flagss = flags = 0;
    MEMZERO(&skey, DBT, 1);
    MEMZERO(&pkey, DBT, 1);
    MEMZERO(&data, DBT, 1);
    data.flags |= DB_DBT_MALLOC;
    pkey.flags |= DB_DBT_MALLOC;
    switch(rb_scan_args(argc, argv, "12", &a, &b, &c)) {
    case 3:
        flags = NUM2INT(c);
        if ((flags & ~DB_RMW) == DB_GET_BOTH) {
            b = bdb_test_dump(obj, &data, b, FILTER_VALUE);
	    data.flags |= DB_DBT_MALLOC;
        }
	break;
    case 2:
	flagss = flags = NUM2INT(b);
	break;
    }
    a = bdb_test_recno(obj, &skey, &srecno, a);
    SET_PARTIAL(dbst, data);
    flags |= TEST_INIT_LOCK(dbst);
    ret = bdb_test_error(dbst->dbp->pget(dbst->dbp, txnid, &skey, &pkey, &data, flags));
    if (ret == DB_NOTFOUND || ret == DB_KEYEMPTY)
        return Qnil;
    if (((flags & ~DB_RMW) == DB_GET_BOTH) ||
	((flags & ~DB_RMW) == DB_SET_RECNO)) {
        return bdb_assoc2(obj, &skey, &pkey, &data);
    }
    return bdb_assoc(obj, &pkey, &data);
}

#endif

#if BDB_VERSION >= 30100

static VALUE
bdb_btree_key_range(obj, a)
    VALUE obj, a;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT key;
    db_recno_t recno;
    DB_KEY_RANGE key_range;
    volatile VALUE b = Qnil;

    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    b = bdb_test_recno(obj, &key, &recno, a);
    bdb_test_error(dbst->dbp->key_range(dbst->dbp, txnid, &key, &key_range, 0));
    return rb_struct_new(bdb_sKeyrange, 
			 rb_float_new(key_range.less),
			 rb_float_new(key_range.equal), 
			 rb_float_new(key_range.greater));
}
#endif

#if BDB_VERSION >= 40416


struct data_flags {
    DB_COMPACT *cdata;
    int flags;
};

static VALUE
bdb_compact_i(obj, dataobj)
    VALUE obj, dataobj;
{
    VALUE key, value;
    char *str;
    struct data_flags *dtf;

    Data_Get_Struct(dataobj, struct data_flags, dtf);
    key = rb_ary_entry(obj, 0);
    value = rb_ary_entry(obj, 1);
    key = rb_obj_as_string(key);
    str = StringValuePtr(key);
    if (strcmp(str, "compact_timeout") == 0) {
	dtf->cdata->compact_timeout = NUM2LONG(value);
    }
    else if (strcmp(str, "compact_fillpercent") == 0) {
	dtf->cdata->compact_fillpercent = NUM2INT(value);
    }
    else if (strcmp(str, "flags") == 0) {
	dtf->flags = NUM2INT(value);
    }
    else {
	rb_warning("Unknown option %s", str);
    }
    return Qnil;
}
    
static VALUE
bdb_treerec_compact(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT start, stop, end;
    DBT *pstart, *pstop;
    DB_COMPACT cdata;
    db_recno_t recno_start, recno_stop;
    int flags;
    VALUE a, b, c, result;

    pstop = pstart = NULL;
    MEMZERO(&cdata, DB_COMPACT, 1);
    flags = 0;
    INIT_TXN(txnid, obj, dbst);
    switch (rb_scan_args(argc, argv, "03", &a, &b, &c)) {
    case 3:
	if (FIXNUM_P(c)) {
	    flags = NUM2INT(c);
	}
	else {
	    struct data_flags *dtf;
	    VALUE dtobj;
		
	    dtobj = Data_Make_Struct(rb_cData, struct data_flags, 0, free, dtf);
	    dtf->cdata = &cdata;
	    dtf->flags = 0;
	    rb_iterate(rb_each, c, bdb_compact_i, dtobj);
	    flags = dtf->flags;
	}
	/* ... */
    case 2:
	if (!NIL_P(b)) {
	    MEMZERO(&stop, DBT, 1);
	    b = bdb_test_recno(obj, &start, &recno_stop, b);
	    pstop = &stop;
	}
	/* ... */
    case 1:
	if (!NIL_P(a)) {
	    MEMZERO(&start, DBT, 1);
	    a = bdb_test_recno(obj, &start, &recno_start, a);
	    pstart = &start;
	}
    }
    MEMZERO(&end, DBT, 1);
    bdb_test_error(dbst->dbp->compact(dbst->dbp, txnid, pstart, pstop, &cdata,
				      flags, &end));
    result = rb_hash_new();
    rb_hash_aset(result, rb_tainted_str_new2("end"), bdb_test_load_key(obj, &end));
    rb_hash_aset(result, rb_tainted_str_new2("compact_deadlock"),
		 INT2NUM(cdata.compact_deadlock));
    rb_hash_aset(result, rb_tainted_str_new2("compact_levels"),
		 INT2NUM(cdata.compact_levels));
    rb_hash_aset(result, rb_tainted_str_new2("compact_pages_free"),
		 INT2NUM(cdata.compact_pages_free));
    rb_hash_aset(result, rb_tainted_str_new2("compact_pages_examine"),
		 INT2NUM(cdata.compact_pages_examine));
    rb_hash_aset(result, rb_tainted_str_new2("compact_pages_truncated"),
		 INT2NUM(cdata.compact_pages_truncated));
    return result;
}

#endif


#if BDB_VERSION >= 20600

static VALUE
bdb_count(obj, a)
    VALUE obj, a;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    int ret, flags27;
    db_recno_t recno;
    db_recno_t count;
    volatile VALUE b = Qnil;

    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    b = bdb_test_recno(obj, &key, &recno, a);
    MEMZERO(&data, DBT, 1);
    data.flags |= DB_DBT_MALLOC;
    SET_PARTIAL(dbst, data);
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
    flags27 = TEST_INIT_LOCK(dbst);
    bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_SET | flags27),
		    dbcp->c_close(dbcp), ret);
    if (ret == DB_NOTFOUND) {
        dbcp->c_close(dbcp);
        return INT2NUM(0);
    }
#if BDB_VERSION >= 30100
    bdb_cache_error(dbcp->c_count(dbcp, &count, 0), dbcp->c_close(dbcp), ret);
    dbcp->c_close(dbcp);
    return INT2NUM(count);
#else
    count = 1;
    while (1) {
        bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_NEXT_DUP | flags27),
			dbcp->c_close(dbcp), ret);
        if (ret == DB_NOTFOUND) {
            dbcp->c_close(dbcp);
            return INT2NUM(count);
        }
        if (ret == DB_KEYEMPTY) continue;
        FREE_KEY(dbst, key);
        if (data.flags & DB_DBT_MALLOC)
            free(data.data);
        count++;
    }
    return INT2NUM(-1);
#endif
}

#endif

#if BDB_VERSION >= 30000

static VALUE
bdb_consume(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT key, data;
    DBC *dbcp;
    int ret;
    db_recno_t recno;

    rb_secure(4);
    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    MEMZERO(&data, DBT, 1);
    recno = 1;
    key.data = &recno;
    key.size = sizeof(db_recno_t);
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
    bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_CONSUME),
		    dbcp->c_close(dbcp), ret);
    dbcp->c_close(dbcp);
    if (ret == DB_NOTFOUND) {
	return Qnil;
    }
    return bdb_assoc(obj, &key, &data);
}

#endif

static VALUE
bdb_has_key(obj, key)
    VALUE obj, key;
{
    return (bdb_get_internal(1, &key, obj, Qundef, 0) == Qundef)?Qfalse:Qtrue;
}

static VALUE
bdb_has_both_internal(obj, a, b, flag)
    VALUE obj, a, b, flag;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    DBT datas;
    int flagss;
    int ret, flags;
    db_recno_t recno;
    volatile VALUE c = Qnil;
    volatile VALUE d = Qnil;

    INIT_TXN(txnid, obj, dbst);
    flagss = flags = 0;
    MEMZERO(&key, DBT, 1);
    MEMZERO(&data, DBT, 1);
    MEMZERO(&datas, DBT, 1);
    c = bdb_test_recno(obj, &key, &recno, a);
    d = bdb_test_dump(obj, &datas, b, FILTER_VALUE);
    data.flags |= DB_DBT_MALLOC;
    SET_PARTIAL(dbst, data);
    flags |= TEST_INIT_LOCK(dbst);
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
#endif
    bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_SET),
		    dbcp->c_close(dbcp), ret);
    if (ret == DB_NOTFOUND || ret == DB_KEYEMPTY) {
	dbcp->c_close(dbcp);
        return (flag == Qtrue)?Qnil:Qfalse;
    }
    if (datas.size == data.size &&
	memcmp(datas.data, data.data, data.size) == 0) {
	dbcp->c_close(dbcp);
	if (flag == Qtrue) {
	    return bdb_assoc(obj, &key, &data);
	}
	else {
	    FREE_KEY(dbst, key);
	    free(data.data);
	    return Qtrue;
	}
    }
#if BDB_VERSION < 20600
#define DB_NEXT_DUP 0
    FREE_KEY(dbst, key);
    free(data.data);
    dbcp->c_close(dbcp);
    return (flag == Qtrue)?Qnil:Qfalse;
#endif
#if BDB_VERSION < 30100
    if (RECNUM_TYPE(dbst)) {
	free(data.data);
	dbcp->c_close(dbcp);
	return Qfalse;
    }
#endif
    while (1) {
	FREE_KEY(dbst, key);
	free(data.data);
	MEMZERO(&data, DBT, 1);
	data.flags |= DB_DBT_MALLOC;
	bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_NEXT_DUP),
			dbcp->c_close(dbcp), ret);
	if (ret == DB_NOTFOUND || ret == DB_KEYEMPTY) {
	    dbcp->c_close(dbcp);
	    return Qfalse;
	}
	if (datas.size == data.size &&
	    memcmp(datas.data, data.data, data.size) == 0) {
	    FREE_KEY(dbst, key);
	    free(data.data);
	    dbcp->c_close(dbcp);
	    return Qtrue;
	}
    }
    return Qfalse;
}

static VALUE
bdb_has_both(obj, a, b)
    VALUE obj, a, b;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBT key, data;
    int ret, flags;
    db_recno_t recno;
    volatile VALUE c = Qnil;
    volatile VALUE d = Qnil;

#if BDB_VERSION < 20600
    return bdb_has_both_internal(obj, a, b, Qfalse);
#else
    INIT_TXN(txnid, obj, dbst);
#if BDB_VERSION < 30100
    if (RECNUM_TYPE(dbst)) {
	return bdb_has_both_internal(obj, a, b, Qfalse);
    }
#endif
    MEMZERO(&key, DBT, 1);
    MEMZERO(&data, DBT, 1);
    c = bdb_test_recno(obj, &key, &recno, a);
    d = bdb_test_dump(obj, &data, b, FILTER_VALUE);
    data.flags |= DB_DBT_MALLOC;
    SET_PARTIAL(dbst, data);
    flags = DB_GET_BOTH | TEST_INIT_LOCK(dbst);
    ret = bdb_test_error(dbst->dbp->get(dbst->dbp, txnid, &key, &data, flags));
    if (ret == DB_NOTFOUND || ret == DB_KEYEMPTY)
        return Qfalse;
    free(data.data);
    return Qtrue;
#endif
}

VALUE
bdb_del(obj, a)
    VALUE a, obj;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    int flag = 0;
    DBT key;
    int ret;
    db_recno_t recno;
    volatile VALUE b = Qnil;

    rb_secure(4);
    INIT_TXN(txnid, obj, dbst);
    if (txnid == NULL && (dbst->options & BDB_AUTO_COMMIT)) {
        flag |= DB_AUTO_COMMIT;
    }
    MEMZERO(&key, DBT, 1);
    b = bdb_test_recno(obj, &key, &recno, a);
    ret = bdb_test_error(dbst->dbp->del(dbst->dbp, txnid, &key, flag));
    if (ret == DB_NOTFOUND || ret == DB_KEYEMPTY)
        return Qnil;
    else
        return obj;
}

static VALUE
bdb_empty(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    int ret, flags;
    db_recno_t recno;

    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
#endif
    SET_PARTIAL(dbst, data);
    flags = TEST_INIT_LOCK(dbst);
    bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_FIRST | flags),
		    dbcp->c_close(dbcp), ret);
    if (ret == DB_NOTFOUND) {
        dbcp->c_close(dbcp);
        return Qtrue;
    }
    FREE_KEY(dbst, key);
    free(data.data);
    dbcp->c_close(dbcp);
    return Qfalse;
}

static VALUE
bdb_lgth_intern(obj, delete)
    VALUE obj, delete;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    int ret, value, flags;
    db_recno_t recno;

    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
    value = 0;
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
#endif
    SET_PARTIAL(dbst, data);
    flags = TEST_INIT_LOCK(dbst);
    do {
        bdb_cache_error(dbcp->c_get(dbcp, &key, &data, DB_NEXT | flags),
			dbcp->c_close(dbcp), ret);
        if (ret == DB_NOTFOUND) {
            dbcp->c_close(dbcp);
            return INT2NUM(value);
        }
	if (ret == DB_KEYEMPTY) continue;
	FREE_KEY(dbst, key);
	free(data.data);
        value++;
	if (delete == Qtrue) {
	    bdb_test_error(dbcp->c_del(dbcp, 0));
	}
    } while (1);
    return INT2NUM(-1);
}

static VALUE
bdb_length(obj)
    VALUE obj;
{
    return bdb_lgth_intern(obj, Qfalse);
}

typedef struct {
    int sens;
    VALUE replace;
    VALUE db;
    VALUE set;
    DBC *dbcp;
#if BDB_VERSION >= 30300
    void *data;
    int len;
#endif
    int primary;
    int type;
} eachst;

static VALUE
bdb_each_ensure(st)
    eachst *st;
{
#if BDB_VERSION >= 30300
    if (st->len && st->data) {
	free(st->data);
    }
#endif
    st->dbcp->c_close(st->dbcp);
    return Qnil;
}

static void
bdb_treat(st, pkey, key, data)
    eachst *st;
    DBT *pkey, *key, *data;
{
    bdb_DB *dbst;
    DBC *dbcp;
    VALUE res = Qnil;

    GetDB(st->db, dbst);
    dbcp = st->dbcp;
    switch (st->type) {
    case BDB_ST_DUPU:
	FREE_KEY(dbst, *key);
	res = bdb_test_load(st->db, data, FILTER_VALUE);
	if (TYPE(st->replace) == T_ARRAY) {
	    rb_ary_push(st->replace, res);
	}
	else {
	    rb_yield(res);
	}
	break;
    case BDB_ST_DUPKV:
	rb_yield(bdb_assoc_dyna(st->db, key, data));
	break;
    case BDB_ST_DUPVAL:
	res = test_load_dyna(st->db, key, data);
	if (TYPE(st->replace) == T_ARRAY) {
	    rb_ary_push(st->replace, res);
	}
	else {
	    rb_yield(res);
	}
	break;
    case BDB_ST_KEY:
	if (data->flags & DB_DBT_MALLOC) {
	    free(data->data);
            data->flags &= ~DB_DBT_MALLOC;
	}
	rb_yield(bdb_test_load_key(st->db, key));
	break;

    case BDB_ST_VALUE:
	FREE_KEY(dbst, *key);
	res = rb_yield(bdb_test_load(st->db, data, FILTER_VALUE));
	if (st->replace == Qtrue) {
	    MEMZERO(data, DBT, 1);
	    res = bdb_test_dump(st->db, data, res, FILTER_VALUE);
	    SET_PARTIAL(dbst, *data);
	    bdb_test_error(dbcp->c_put(dbcp, key, data, DB_CURRENT));
	}
	else if (st->replace != Qfalse) {
	    rb_ary_push(st->replace, res);
	}
	break;
     case BDB_ST_SELECT:
	res = bdb_assoc(st->db, key, data);
	if (RTEST(rb_yield(res))) {
	    rb_ary_push(st->replace, res);
	}
	break;
    case BDB_ST_KV:
	if (st->primary) {
	    rb_yield(bdb_assoc3(st->db, key, pkey, data));
	}
	else {
	    rb_yield(bdb_assoc_dyna(st->db, key, data));
	}
	break;
    case BDB_ST_REJECT:
    {
	VALUE ary = bdb_assoc(st->db, key, data);
	if (!RTEST(rb_yield(ary))) {
	    rb_hash_aset(st->replace, RARRAY(ary)->ptr[0], RARRAY(ary)->ptr[1]);
	}
	break;
    }
    case BDB_ST_DELETE:
	if (RTEST(rb_yield(bdb_assoc(st->db, key, data)))) {
	    bdb_test_error(dbcp->c_del(dbcp, 0));
	}
	break;
    }
}

static VALUE
bdb_i_each_kv(st)
    eachst *st;
{
    bdb_DB *dbst;
    DBC *dbcp;
    DBT pkey, key, data, orig;
    int ret, init = Qfalse, prefix = Qfalse;
    db_recno_t recno;
    volatile VALUE res = Qnil;
    
    prefix = st->type & BDB_ST_PREFIX;
    st->type &= ~BDB_ST_PREFIX;
    GetDB(st->db, dbst);
    dbcp = st->dbcp;
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&orig, DBT, 1);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
    SET_PARTIAL(dbst, data);
    MEMZERO(&pkey, DBT, 1);
    pkey.flags = DB_DBT_MALLOC;
    if (!NIL_P(st->set)) {
	res = bdb_test_recno(st->db, &key, &recno, st->set);
        if (prefix) {
            init = Qtrue;
            orig.size = key.size;
            orig.data = ALLOCA_N(char, key.size);
            MEMCPY(orig.data, key.data, char, key.size);
        }
#if BDB_VERSION >= 30300
	if (st->type == BDB_ST_KV && st->primary) {
	    ret = bdb_test_error(dbcp->c_pget(dbcp, &key, &pkey, &data, 
                                              (st->type & BDB_ST_DUP)?DB_SET:
                                              DB_SET_RANGE));
	}
	else
#endif
	{
#if BDB_VERSION < 20600
	    key.flags |= DB_DBT_MALLOC;
#endif
	    ret = bdb_test_error(dbcp->c_get(dbcp, &key, &data, 
                                             (st->type & BDB_ST_DUP)?DB_SET:
                                             DB_SET_RANGE));
#if BDB_VERSION < 20600
	    key.flags &= ~DB_DBT_MALLOC;
#endif
	}
	if (ret == DB_NOTFOUND) {
	    return Qfalse;
	}
        bdb_treat(st, &pkey, &key, &data);
    }
    do {
#if BDB_VERSION >= 30300
	if (st->type == BDB_ST_KV && st->primary) {
	    ret = bdb_test_error(dbcp->c_pget(dbcp, &key, &pkey, &data, st->sens));
	}
	else
#endif
	{
#if BDB_VERSION < 20600
	    key.flags |= DB_DBT_MALLOC;
#endif
	    ret = bdb_test_error(dbcp->c_get(dbcp, &key, &data, st->sens));
#if BDB_VERSION < 20600
	    key.flags &= ~DB_DBT_MALLOC;
#endif
	}
        if (ret == DB_NOTFOUND) {
            return Qnil;
        }
	if (ret == DB_KEYEMPTY) continue;
        if (prefix) {
            if (!init) {
                init = Qtrue;
                orig.size = key.size;
                orig.data = ALLOCA_N(char, key.size);
                MEMCPY(orig.data, key.data, char, key.size);
            }
            if (key.size >= orig.size &&
                !memcmp(key.data, orig.data, orig.size)) {
                bdb_treat(st, &pkey, &key, &data);
            }
        }
        else {
            bdb_treat(st, &pkey, &key, &data);
        }
    } while (1);
    return Qnil;
}

#if BDB_VERSION >= 30300

static VALUE
bdb_i_each_kv_bulk(st)
    eachst *st;
{
    bdb_DB *dbst;
    DBC *dbcp;
    DBT key, data;
    DBT rkey, rdata;
    DBT pkey;
    int ret, init;
    db_recno_t recno;
    void *p;
    volatile VALUE res = Qnil;
    
    GetDB(st->db, dbst);
    dbcp = st->dbcp;
    MEMZERO(&key, DBT, 1);
    MEMZERO(&pkey, DBT, 1);
    MEMZERO(&rkey, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    MEMZERO(&rdata, DBT, 1);
    st->data = data.data = (void *)ALLOC_N(char, st->len);
    data.ulen = st->len;
    data.flags = DB_DBT_USERMEM;
    SET_PARTIAL(dbst, data);
    SET_PARTIAL(dbst, rdata);
    init = 1;
    do {
	if (init && !NIL_P(st->set)) {
	    res = bdb_test_recno(st->db, &key, &recno, st->set);
	    ret = bdb_test_error(dbcp->c_get(dbcp, &key, &data, 
                                             ((st->type & BDB_ST_DUP)?DB_SET:
                                              DB_SET_RANGE)|DB_MULTIPLE_KEY));
	    init = 0;
	}
	else {
	    ret = bdb_test_error(dbcp->c_get(dbcp, &key, &data, st->sens | DB_MULTIPLE_KEY));
	}
        if (ret == DB_NOTFOUND) {
            return Qnil;
        }
	if (ret == DB_KEYEMPTY) continue;
	for (DB_MULTIPLE_INIT(p, &data);;) {
	    if (RECNUM_TYPE(dbst)) {
		DB_MULTIPLE_RECNO_NEXT(p, &data, recno, 
				       rdata.data, rdata.size);
	    }
	    else {
		DB_MULTIPLE_KEY_NEXT(p, &data, rkey.data, rkey.size,
				     rdata.data, rdata.size);
	    }
	    if (p == NULL) break;
	    bdb_treat(st, 0, &rkey, &rdata);
	}
    } while (1);
    return Qnil;
}

#endif

VALUE
bdb_each_kvc(argc, argv, obj, sens, replace, type)
    VALUE obj, *argv;
    int argc, sens;
    VALUE replace;
    int type;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    eachst st;
    int flags = 0;

    if (argc && TYPE(argv[argc - 1]) == T_HASH) {
	VALUE g, f = argv[argc - 1];
	if ((g = rb_hash_aref(f, rb_intern("flags"))) != RHASH(f)->ifnone ||
	    (g = rb_hash_aref(f, rb_str_new2("flags"))) != RHASH(f)->ifnone) {
	    flags = NUM2INT(g);
	}
	argc--;
    }
	
    MEMZERO(&st, eachst, 1);

#if BDB_VERSION >= 30300
    {
	VALUE bulkv = Qnil;

	st.set = Qnil;
	if (type & BDB_ST_ONE) {
	    rb_scan_args(argc, argv, "01", &st.set);
	}
	else {
	    if (type & BDB_ST_DUP) {
		rb_scan_args(argc, argv, "11", &st.set, &bulkv);
	    }
	    else {
		if (rb_scan_args(argc, argv, "02", &st.set, &bulkv) == 2) {
		    if (bulkv == Qtrue || bulkv == Qfalse) {
			st.primary = RTEST(bulkv);
			bulkv = Qnil;
		    }
		}
	    }
	}
	if (!NIL_P(bulkv)) {
	    st.len = 1024 * NUM2INT(bulkv);
	    if (st.len < 0) {
		rb_raise(bdb_eFatal, "negative value for bulk retrieval");
	    }
	}
    }
#else
    if (type & BDB_ST_DUP) {
	if (argc != 1) {
	    rb_raise(bdb_eFatal, "invalid number of arguments (%d for 1)", argc);
	}
	st.set = argv[0];
    }
    else {
	rb_scan_args(argc, argv, "01", &st.set);
    }
#endif
    type &= ~BDB_ST_ONE;
    if ((type & ~BDB_ST_PREFIX) == BDB_ST_DELETE) {
	rb_secure(4);
    }
    INIT_TXN(txnid, obj, dbst);
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, flags));
#endif
    st.db = obj;
    st.dbcp = dbcp;
    st.sens = sens | TEST_INIT_LOCK(dbst);
    st.replace = replace;
    st.type = type & ~BDB_ST_ONE;
#if BDB_VERSION >= 30300
    if (st.len) {
	rb_ensure(bdb_i_each_kv_bulk, (VALUE)&st, bdb_each_ensure, (VALUE)&st);
    }
    else
#endif
    {
	rb_ensure(bdb_i_each_kv, (VALUE)&st, bdb_each_ensure, (VALUE)&st);
    }
    if (replace == Qtrue || replace == Qfalse) {
	return obj;
    }
    else {
	return st.replace;
    }
}

#if BDB_VERSION >= 20600

static VALUE
bdb_get_dup(int argc, VALUE *argv, VALUE obj)
{
    VALUE result = Qfalse;
    if (!rb_block_given_p()) {
	result = rb_ary_new();
    }
    return bdb_each_kvc(argc, argv, obj, DB_NEXT_DUP, result, BDB_ST_DUPU);
}

static VALUE
bdb_common_each_dup(int argc, VALUE *argv, VALUE obj)
{
    if (!rb_block_given_p()) {
	rb_raise(bdb_eFatal, "each_dup called out of an iterator");
    }
    return bdb_each_kvc(argc, argv, obj, DB_NEXT_DUP, Qfalse, BDB_ST_DUPKV);
}

static VALUE
bdb_common_each_dup_val(int argc, VALUE *argv, VALUE obj)
{
    if (!rb_block_given_p()) {
	rb_raise(bdb_eFatal, "each_dup called out of an iterator");
    }
    return bdb_each_kvc(argc, argv, obj, DB_NEXT_DUP, Qfalse, BDB_ST_DUPVAL);
}

static VALUE
bdb_common_dups(int argc, VALUE *argv, VALUE obj)
{
    VALUE result = rb_ary_new();
    return bdb_each_kvc(argc, argv, obj, DB_NEXT_DUP, result, BDB_ST_DUPVAL);
}

#endif

static VALUE
bdb_delete_if(int argc, VALUE *argv, VALUE obj)
{
    return bdb_each_kvc(argc, argv, obj, DB_NEXT, Qfalse, BDB_ST_DELETE | BDB_ST_ONE);
}

static VALUE
bdb_reject(int argc, VALUE *argv, VALUE obj)
{
    return bdb_each_kvc(argc, argv, obj, DB_NEXT, rb_hash_new(), BDB_ST_REJECT);
}

VALUE
bdb_each_value(int argc, VALUE *argv, VALUE obj)
{ 
    return bdb_each_kvc(argc, argv, obj, DB_NEXT, Qfalse, BDB_ST_VALUE);
}

VALUE
bdb_each_eulav(int argc, VALUE *argv, VALUE obj)
{
    return bdb_each_kvc(argc, argv, obj, DB_PREV, Qfalse, BDB_ST_VALUE | BDB_ST_ONE);
}

VALUE
bdb_each_key(int argc, VALUE *argv, VALUE obj)
{ 
    return bdb_each_kvc(argc, argv, obj, DB_NEXT, Qfalse, BDB_ST_KEY); 
}

static VALUE
bdb_each_yek(int argc, VALUE *argv, VALUE obj) 
{ 
    return bdb_each_kvc(argc, argv, obj, DB_PREV, Qfalse, BDB_ST_KEY | BDB_ST_ONE);
}

static VALUE
bdb_each_pair(int argc, VALUE *argv, VALUE obj) 
{
    return bdb_each_kvc(argc, argv, obj, DB_NEXT, Qfalse, BDB_ST_KV);
}

static VALUE
bdb_each_prefix(int argc, VALUE *argv, VALUE obj) 
{
    return bdb_each_kvc(argc, argv, obj, DB_NEXT, Qfalse, BDB_ST_KV | BDB_ST_PREFIX);
}

static VALUE
bdb_each_riap(int argc, VALUE *argv, VALUE obj) 
{
    return bdb_each_kvc(argc, argv, obj, DB_PREV, Qfalse, BDB_ST_KV | BDB_ST_ONE);
}

static VALUE
bdb_each_xiferp(int argc, VALUE *argv, VALUE obj) 
{
    return bdb_each_kvc(argc, argv, obj, DB_PREV, Qfalse, 
                        BDB_ST_KV | BDB_ST_ONE | BDB_ST_PREFIX);
}

static VALUE
bdb_each_pair_prim(int argc, VALUE *argv, VALUE obj) 
{
    VALUE tmp[2] = {Qnil, Qtrue};
    rb_scan_args(argc, argv, "01", tmp);
    return bdb_each_kvc(2, tmp, obj, DB_NEXT, Qfalse, BDB_ST_KV);
}

static VALUE
bdb_each_riap_prim(int argc, VALUE *argv, VALUE obj) 
{
    VALUE tmp[2] = {Qnil, Qtrue};
    rb_scan_args(argc, argv, "01", tmp);
    return bdb_each_kvc(2, tmp, obj, DB_PREV, Qfalse, BDB_ST_KV);
}

VALUE
bdb_to_type(obj, result, flag)
    VALUE obj, result, flag;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    int ret, flags;
    db_recno_t recno;

    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
#endif
    SET_PARTIAL(dbst, data);
    flags = ((flag == Qnil)?DB_PREV:DB_NEXT) | TEST_INIT_LOCK(dbst);
    do {
        bdb_cache_error(dbcp->c_get(dbcp, &key, &data, flags),
			dbcp->c_close(dbcp), ret);
        if (ret == DB_NOTFOUND) {
            dbcp->c_close(dbcp);
            return result;
        }
	if (ret == DB_KEYEMPTY) continue;
	switch (TYPE(result)) {
	case T_ARRAY:
	    if (flag == Qtrue) {
		rb_ary_push(result, bdb_assoc(obj, &key, &data));
	    }
	    else {
		rb_ary_push(result, bdb_test_load(obj, &data, FILTER_VALUE));
	    }
	    break;
	case T_HASH:
	    if (flag == Qtrue) {
		rb_hash_aset(result, bdb_test_load_key(obj, &key), 
			     bdb_test_load(obj, &data, FILTER_VALUE));
	    }
	    else {
		rb_hash_aset(result, bdb_test_load(obj, &data, FILTER_VALUE), 
			     bdb_test_load_key(obj, &key));
	    }
	}

    } while (1);
    return result;
}

static VALUE
bdb_to_a(obj)
    VALUE obj;
{
    return bdb_to_type(obj, rb_ary_new(), Qtrue);
}

static VALUE
each_pair(obj)
    VALUE obj;
{
    return rb_funcall(obj, rb_intern("each_pair"), 0, 0);
}

static VALUE
bdb_update_i(pair, obj)
    VALUE pair, obj;
{
    Check_Type(pair, T_ARRAY);
    if (RARRAY(pair)->len < 2) {
	rb_raise(rb_eArgError, "pair must be [key, value]");
    }
    bdb_put(2, RARRAY(pair)->ptr, obj);
    return Qnil;
}

static VALUE
bdb_update(obj, other)
    VALUE obj, other;
{
    rb_iterate(each_pair, other, bdb_update_i, obj);
    return obj;
}

VALUE
bdb_clear(int argc, VALUE *argv, VALUE obj)
{
#if BDB_VERSION >= 30300
    bdb_DB *dbst;
    DB_TXN *txnid;
    unsigned int count = 0;
#endif
    int flags = 0;

    rb_secure(4);
#if BDB_VERSION >= 30300
    INIT_TXN(txnid, obj, dbst);
    if (txnid == NULL && (dbst->options & BDB_AUTO_COMMIT)) {
      flags |= DB_AUTO_COMMIT;
    }
    bdb_test_error(dbst->dbp->truncate(dbst->dbp, txnid, &count, flags));
    return INT2NUM(count);
#else
    flags = 0;
    if (argc && TYPE(argv[argc - 1]) == T_HASH) {
	VALUE g, f = argv[argc - 1];
	if ((g = rb_hash_aref(f, rb_intern("flags"))) != RHASH(f)->ifnone ||
	    (g = rb_hash_aref(f, rb_str_new2("flags"))) != RHASH(f)->ifnone) {
	    flags = NUM2INT(g);
	}
	argc--;
    }
    if (argc) {
	flags = NUM2INT(argv[0]);
    }
    return bdb_lgth_intern(obj, Qtrue, flags);
#endif
}

static VALUE
bdb_replace(int argc, VALUE *argv, VALUE obj)
{
    VALUE g;
    int flags;

    if (argc == 0 || argc > 2) {
	rb_raise(rb_eArgError, "invalid number of arguments (0 for 1)");
    }
    flags = 0;
    if (TYPE(argv[argc - 1]) == T_HASH) {
	VALUE f = argv[argc - 1];
	if ((g = rb_hash_aref(f, rb_intern("flags"))) != RHASH(f)->ifnone ||
	    (g = rb_hash_aref(f, rb_str_new2("flags"))) != RHASH(f)->ifnone) {
	    flags = NUM2INT(g);
	}
	argc--;
    }
    if (argc == 2) {
	flags = NUM2INT(argv[1]);
    }
    g = INT2FIX(flags);
    bdb_clear(1, &g, obj);
    rb_iterate(each_pair, argv[0], bdb_update_i, obj);
    return obj;
}

static VALUE
bdb_invert(obj)
    VALUE obj;
{
    return bdb_to_type(obj, rb_hash_new(), Qfalse);
}

static VALUE
bdb_to_hash(obj)
    VALUE obj;
{
    return bdb_to_type(obj, rb_hash_new(), Qtrue);
}
 
static VALUE
bdb_kv(obj, type)
    VALUE obj;
    int type;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    int ret, flags;
    db_recno_t recno;
    VALUE ary;

    ary = rb_ary_new();
    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
#endif
    SET_PARTIAL(dbst, data);
    flags = DB_NEXT | TEST_INIT_LOCK(dbst);
    do {
        bdb_cache_error(dbcp->c_get(dbcp, &key, &data, flags),
			dbcp->c_close(dbcp), ret);
        if (ret == DB_NOTFOUND) {
            dbcp->c_close(dbcp);
            return ary;
        }
	if (ret == DB_KEYEMPTY) continue;
	switch (type) {
	case BDB_ST_VALUE:
	    FREE_KEY(dbst, key);
	    rb_ary_push(ary, bdb_test_load(obj, &data, FILTER_VALUE));
	    break;
	case BDB_ST_KEY:
	    free(data.data);
	    rb_ary_push(ary, bdb_test_load_key(obj, &key));
	    break;
	}
    } while (1);
    return ary;
}

static VALUE
bdb_values(obj)
    VALUE obj;
{
    return bdb_kv(obj, BDB_ST_VALUE);
}

static VALUE
bdb_keys(obj)
    VALUE obj;
{
    return bdb_kv(obj, BDB_ST_KEY);
}

VALUE
bdb_internal_value(obj, a, b, sens)
    VALUE obj, a, b;
    int sens;
{
    bdb_DB *dbst;
    DB_TXN *txnid;
    DBC *dbcp;
    DBT key, data;
    int ret, flags;
    db_recno_t recno;

    INIT_TXN(txnid, obj, dbst);
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    data.flags = DB_DBT_MALLOC;
#if BDB_VERSION < 20600
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp));
#else
    bdb_test_error(dbst->dbp->cursor(dbst->dbp, txnid, &dbcp, 0));
#endif
    SET_PARTIAL(dbst, data);
    flags = sens | TEST_INIT_LOCK(dbst);
    do {
        bdb_cache_error(dbcp->c_get(dbcp, &key, &data, flags),
			dbcp->c_close(dbcp), ret);
        if (ret == DB_NOTFOUND) {
            dbcp->c_close(dbcp);
            return (b == Qfalse)?Qfalse:Qnil;
        }
	if (ret == DB_KEYEMPTY) continue;
	if (rb_equal(a, bdb_test_load(obj, &data, FILTER_VALUE)) == Qtrue) {
	    VALUE d;
	    
	    dbcp->c_close(dbcp);
	    if (b == Qfalse) {
		d = Qtrue;
		FREE_KEY(dbst, key);
	    }
	    else {
		d = bdb_test_load_key(obj, &key);
	    }
	    return  d;
	}
	FREE_KEY(dbst, key);
    } while (1);
    return (b == Qfalse)?Qfalse:Qnil;
}

VALUE
bdb_index(obj, a)
    VALUE obj, a;
{
    return bdb_internal_value(obj, a, Qtrue, DB_NEXT);
}

static VALUE
bdb_indexes(int argc, VALUE *argv, VALUE obj)
{
    VALUE indexes;
    int i;

#if HAVE_RB_ARY_VALUES_AT
    rb_warn("Common#%s is deprecated; use Common#values_at",
#if HAVE_RB_FRAME_THIS_FUNC
	    rb_id2name(rb_frame_this_func()));
#else
	    rb_id2name(rb_frame_last_func()));
#endif
#endif
    indexes = rb_ary_new2(argc);
    for (i = 0; i < argc; i++) {
	RARRAY(indexes)->ptr[i] = bdb_get(1, &argv[i], obj);
    }
    RARRAY(indexes)->len = i;
    return indexes;
}

static VALUE
bdb_values_at(int argc, VALUE *argv, VALUE obj)
{
    VALUE result = rb_ary_new2(argc);
    long i;

    for (i = 0; i < argc; i++) {
	rb_ary_push(result, bdb_get(1, &argv[i], obj));
    }
    return result;
}

static VALUE
bdb_select(int argc, VALUE *argv, VALUE obj)
{
    VALUE result = rb_ary_new();

    if (rb_block_given_p()) {
	if (argc > 0) {
	    rb_raise(rb_eArgError, "wrong number arguments(%d for 0)", argc);
	}
	return bdb_each_kvc(argc, argv, obj, DB_NEXT, result, BDB_ST_SELECT);
    }
    rb_warn("Common#select(index..) is deprecated; use Common#values_at");
    return bdb_values_at(argc, argv, obj);
}

VALUE
bdb_has_value(obj, a)
    VALUE obj, a;
{
    return bdb_internal_value(obj, a, Qfalse, DB_NEXT);
}

static VALUE
bdb_sync(obj)
    VALUE obj;
{
    bdb_DB *dbst;

    if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
	rb_raise(rb_eSecurityError, "Insecure: can't sync the database");
    GetDB(obj, dbst);
    bdb_test_error(dbst->dbp->sync(dbst->dbp, 0));
    return Qtrue;
}

#if BDB_VERSION >= 30000
static VALUE
bdb_hash_stat(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    DB_HASH_STAT *bdb_stat;
    VALUE hash, flagv;
    int flags = 0;
#if BDB_VERSION >= 4030
    DB_TXN *txnid = NULL;
#endif

    if (rb_scan_args(argc, argv, "01", &flagv) == 1) {
	flags = NUM2INT(flagv);
    }
    GetDB(obj, dbst);
#if BDB_VERSION < 30300
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, 0, flags));
#else
#if BDB_VERSION >= 40300
    if (RTEST(dbst->txn)) {
        bdb_TXN *txnst;

        GetTxnDB(dbst->txn, txnst);
        txnid = txnst->txnid;
    }
    bdb_test_error(dbst->dbp->stat(dbst->dbp, txnid, &bdb_stat, flags));
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, flags));
#endif
#endif
    hash = rb_hash_new();
    rb_hash_aset(hash, rb_tainted_str_new2("hash_magic"), INT2NUM(bdb_stat->hash_magic));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_version"), INT2NUM(bdb_stat->hash_version));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_pagesize"), INT2NUM(bdb_stat->hash_pagesize));
#if BDB_VERSION >= 30114
    rb_hash_aset(hash, rb_tainted_str_new2("hash_nkeys"), INT2NUM(bdb_stat->hash_nkeys));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_nrecs"), INT2NUM(bdb_stat->hash_nkeys));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_ndata"), INT2NUM(bdb_stat->hash_ndata));
#else
    rb_hash_aset(hash, rb_tainted_str_new2("hash_nrecs"), INT2NUM(bdb_stat->hash_nrecs));
#endif
#if BDB_VERSION < 40100
    rb_hash_aset(hash, rb_tainted_str_new2("hash_nelem"), INT2NUM(bdb_stat->hash_nelem));
#endif
    rb_hash_aset(hash, rb_tainted_str_new2("hash_ffactor"), INT2NUM(bdb_stat->hash_ffactor));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_buckets"), INT2NUM(bdb_stat->hash_buckets));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_free"), INT2NUM(bdb_stat->hash_free));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_bfree"), INT2NUM(bdb_stat->hash_bfree));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_bigpages"), INT2NUM(bdb_stat->hash_bigpages));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_big_bfree"), INT2NUM(bdb_stat->hash_big_bfree));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_overflows"), INT2NUM(bdb_stat->hash_overflows));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_ovfl_free"), INT2NUM(bdb_stat->hash_ovfl_free));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_dup"), INT2NUM(bdb_stat->hash_dup));
    rb_hash_aset(hash, rb_tainted_str_new2("hash_dup_free"), INT2NUM(bdb_stat->hash_dup_free));
    free(bdb_stat);
    return hash;
}
#endif

VALUE
bdb_tree_stat(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    DB_BTREE_STAT *bdb_stat;
    VALUE hash, flagv;
    char pad;
    int flags = 0;
#if BDB_VERSION >= 40300
    DB_TXN *txnid = NULL;
#endif

    if (rb_scan_args(argc, argv, "01", &flagv) == 1) {
	flags = NUM2INT(flagv);
    }
    GetDB(obj, dbst);
#if BDB_VERSION >= 30300
#if BDB_VERSION >= 40300
    if (RTEST(dbst->txn)) {
        bdb_TXN *txnst;

        GetTxnDB(dbst->txn, txnst);
        txnid = txnst->txnid;
    }
    bdb_test_error(dbst->dbp->stat(dbst->dbp, txnid, &bdb_stat, flags));
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, flags));
#endif
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, 0, flags));
#endif
    hash = rb_hash_new();
    rb_hash_aset(hash, rb_tainted_str_new2("bt_magic"), INT2NUM(bdb_stat->bt_magic));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_version"), INT2NUM(bdb_stat->bt_version));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_dup_pg"), INT2NUM(bdb_stat->bt_dup_pg));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_dup_pgfree"), INT2NUM(bdb_stat->bt_dup_pgfree));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_free"), INT2NUM(bdb_stat->bt_free));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_int_pg"), INT2NUM(bdb_stat->bt_int_pg));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_int_pgfree"), INT2NUM(bdb_stat->bt_int_pgfree));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_leaf_pg"), INT2NUM(bdb_stat->bt_leaf_pg));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_leaf_pgfree"), INT2NUM(bdb_stat->bt_leaf_pgfree));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_levels"), INT2NUM(bdb_stat->bt_levels));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_minkey"), INT2NUM(bdb_stat->bt_minkey));
#if BDB_VERSION >= 30100
    rb_hash_aset(hash, rb_tainted_str_new2("bt_nrecs"), INT2NUM(bdb_stat->bt_nkeys));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_nkeys"), INT2NUM(bdb_stat->bt_nkeys));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_ndata"), INT2NUM(bdb_stat->bt_ndata));
#else
    rb_hash_aset(hash, rb_tainted_str_new2("bt_nrecs"), INT2NUM(bdb_stat->bt_nrecs));
#endif
    rb_hash_aset(hash, rb_tainted_str_new2("bt_over_pg"), INT2NUM(bdb_stat->bt_over_pg));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_over_pgfree"), INT2NUM(bdb_stat->bt_over_pgfree));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_pagesize"), INT2NUM(bdb_stat->bt_pagesize));
    rb_hash_aset(hash, rb_tainted_str_new2("bt_re_len"), INT2NUM(bdb_stat->bt_re_len));
    pad = (char)bdb_stat->bt_re_pad;
    rb_hash_aset(hash, rb_tainted_str_new2("bt_re_pad"), rb_tainted_str_new(&pad, 1));
    free(bdb_stat);
    return hash;
}

#if BDB_VERSION >= 30000
static VALUE
bdb_queue_stat(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    DB_QUEUE_STAT *bdb_stat;
    VALUE hash, flagv;
    char pad;
    int flags = 0;
#if BDB_VERSION >= 40300
    DB_TXN *txnid = NULL;
#endif

    if (rb_scan_args(argc, argv, "01", &flagv) == 1) {
	flags = NUM2INT(flagv);
    }
    GetDB(obj, dbst);
#if BDB_VERSION < 30300
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, 0, flags));
#else
#if BDB_VERSION >= 40300
    if (RTEST(dbst->txn)) {
        bdb_TXN *txnst;

        GetTxnDB(dbst->txn, txnst);
        txnid = txnst->txnid;
    }
    bdb_test_error(dbst->dbp->stat(dbst->dbp, txnid, &bdb_stat, flags));
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, flags));
#endif
#endif
    hash = rb_hash_new();
    rb_hash_aset(hash, rb_tainted_str_new2("qs_magic"), INT2NUM(bdb_stat->qs_magic));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_version"), INT2NUM(bdb_stat->qs_version));
#if BDB_VERSION >= 30114
    rb_hash_aset(hash, rb_tainted_str_new2("qs_nrecs"), INT2NUM(bdb_stat->qs_nkeys));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_nkeys"), INT2NUM(bdb_stat->qs_nkeys));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_ndata"), INT2NUM(bdb_stat->qs_ndata));
#else
    rb_hash_aset(hash, rb_tainted_str_new2("qs_nrecs"), INT2NUM(bdb_stat->qs_nrecs));
#endif
    rb_hash_aset(hash, rb_tainted_str_new2("qs_pages"), INT2NUM(bdb_stat->qs_pages));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_pagesize"), INT2NUM(bdb_stat->qs_pagesize));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_pgfree"), INT2NUM(bdb_stat->qs_pgfree));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_re_len"), INT2NUM(bdb_stat->qs_re_len));
    pad = (char)bdb_stat->qs_re_pad;
    rb_hash_aset(hash, rb_tainted_str_new2("qs_re_pad"), rb_tainted_str_new(&pad, 1));
#if BDB_VERSION < 30200
    rb_hash_aset(hash, rb_tainted_str_new2("qs_start"), INT2NUM(bdb_stat->qs_start));
#endif
    rb_hash_aset(hash, rb_tainted_str_new2("qs_first_recno"), INT2NUM(bdb_stat->qs_first_recno));
    rb_hash_aset(hash, rb_tainted_str_new2("qs_cur_recno"), INT2NUM(bdb_stat->qs_cur_recno));
    free(bdb_stat);
    return hash;
}

static VALUE
bdb_queue_padlen(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    DB_QUEUE_STAT *bdb_stat;
    VALUE hash;
    char pad;
#if BDB_VERSION >= 40300
    DB_TXN *txnid = NULL;
#endif

    GetDB(obj, dbst);
#if BDB_VERSION < 30300
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, 0, 0));
#else
#if BDB_VERSION >= 40300
    if (RTEST(dbst->txn)) {
        bdb_TXN *txnst;

        GetTxnDB(dbst->txn, txnst);
        txnid = txnst->txnid;
    }
    bdb_test_error(dbst->dbp->stat(dbst->dbp, txnid, &bdb_stat, 0));
#else
    bdb_test_error(dbst->dbp->stat(dbst->dbp, &bdb_stat, 0));
#endif
#endif
    pad = (char)bdb_stat->qs_re_pad;
    hash = rb_assoc_new(rb_tainted_str_new(&pad, 1), INT2NUM(bdb_stat->qs_re_len));
    free(bdb_stat);
    return hash;
}
#endif

static VALUE
bdb_set_partial(obj, a, b)
    VALUE obj, a, b;
{
    bdb_DB *dbst;
    VALUE ret;

    GetDB(obj, dbst);
    if (dbst->marshal) {
	rb_raise(bdb_eFatal, "set_partial is not implemented with Marshal");
    }
    ret = rb_ary_new2(3);
    rb_ary_push(ret, (dbst->partial == DB_DBT_PARTIAL)?Qtrue:Qfalse);
    rb_ary_push(ret, INT2NUM(dbst->doff));
    rb_ary_push(ret, INT2NUM(dbst->dlen));
    dbst->doff = NUM2UINT(a);
    dbst->dlen = NUM2UINT(b);
    dbst->partial = DB_DBT_PARTIAL;
    return ret;
}

static VALUE
bdb_clear_partial(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    VALUE ret;

    GetDB(obj, dbst);
    if (dbst->marshal) {
	rb_raise(bdb_eFatal, "set_partial is not implemented with Marshal");
    }
    ret = rb_ary_new2(3);
    rb_ary_push(ret, (dbst->partial == DB_DBT_PARTIAL)?Qtrue:Qfalse);
    rb_ary_push(ret, INT2NUM(dbst->doff));
    rb_ary_push(ret, INT2NUM(dbst->dlen));
    dbst->doff = dbst->dlen = dbst->partial = 0;
    return ret;
}

#if BDB_VERSION >= 30000
static VALUE
bdb_i_create(obj)
    VALUE obj;
{
    DB *dbp;
    bdb_ENV *envst = 0;
    DB_ENV *envp;
    bdb_DB *dbst;
    VALUE ret, env;

    envp = NULL;
    env = 0;
    if (rb_obj_is_kind_of(obj, bdb_cEnv)) {
        GetEnvDB(obj, envst);
        envp = envst->envp;
        env = obj;
    }
    bdb_test_error(db_create(&dbp, envp, 0));
    dbp->set_errpfx(dbp, "BDB::");
    dbp->set_errcall(dbp, bdb_env_errcall);
    ret = Data_Make_Struct(bdb_cCommon, bdb_DB, bdb_mark, bdb_free, dbst);
    rb_obj_call_init(ret, 0, 0);
    dbst->env = env;
    dbst->dbp = dbp;
    if (envp) {
	dbst->options |= envst->options & BDB_INIT_LOCK;
    }
    return ret;
}
#endif

static VALUE
bdb_s_upgrade(int argc, VALUE *argv, VALUE obj)
{
#if BDB_VERSION >= 30000
    bdb_DB *dbst;
    VALUE a, b;
    int flags;
    VALUE val;

    rb_secure(4);
    flags = 0;
    if (rb_scan_args(argc, argv, "11", &a, &b) == 2) {
	flags = NUM2INT(b);
    }
    SafeStringValue(a);
    val = bdb_i_create(obj);
    GetDB(val, dbst);
    bdb_test_error(dbst->dbp->upgrade(dbst->dbp, StringValuePtr(a), flags));
    return val;
#else
    rb_raise(bdb_eFatal, "You can't upgrade a database with this version of DB");
#endif
}

static VALUE
bdb_s_remove(int argc, VALUE *argv, VALUE obj)
{
#if BDB_VERSION >= 30000
    bdb_DB *dbst;
    VALUE a, b, c;
    char *name, *subname;

    rb_secure(2);
    c = bdb_i_create(obj);
    GetDB(c, dbst);
    name = subname = NULL;
    a = b = Qnil;
    if (rb_scan_args(argc, argv, "11", &a, &b) == 2) {
        if (!NIL_P(b)) {
	    SafeStringValue(b);
            subname = StringValuePtr(b);
        }
    }
    SafeStringValue(a);
    name = StringValuePtr(a);
    bdb_test_error(dbst->dbp->remove(dbst->dbp, name, subname, 0));
#else
    rb_raise(bdb_eFatal, "You can't remove a database with this version of DB");
#endif
    return Qtrue;
}

static VALUE
bdb_s_rename(int argc, VALUE *argv, VALUE obj)
{
#if BDB_VERSION >= 30114
    bdb_DB *dbst;
    VALUE a, b, c;
    char *name, *subname, *newname;

    rb_secure(2);
    c = bdb_i_create(obj);
    GetDB(c, dbst);
    name = subname = NULL;
    a = b = c = Qnil;
    rb_scan_args(argc, argv, "30", &a, &b, &c);
    if (!NIL_P(b)) {
	SafeStringValue(b);
	subname = StringValuePtr(b);
    }
    SafeStringValue(a);
    SafeStringValue(c);
    name = StringValuePtr(a);
    newname = StringValuePtr(c);
    bdb_test_error(dbst->dbp->rename(dbst->dbp, name, subname, newname, 0));
#else
    rb_raise(bdb_eFatal, "You can't rename a database with this version of DB");
#endif
    return Qtrue;
}

#if BDB_VERSION >= 20600

static VALUE
bdb_i_joinclose(st)
    eachst *st;
{
    bdb_DB *dbst;

    GetDB(st->db, dbst);
    if (st->dbcp && dbst && dbst->dbp) {
	st->dbcp->c_close(st->dbcp);
    }
    return Qnil;
}

 
static VALUE
bdb_i_join(st)
    eachst *st;
{
    int ret;
    DBT key, data;
    db_recno_t recno;
    bdb_DB *dbst;

    GetDB(st->db, dbst);
    MEMZERO(&key, DBT, 1);
    INIT_RECNO(dbst, key, recno);
    MEMZERO(&data, DBT, 1);
    data.flags |= DB_DBT_MALLOC;
    SET_PARTIAL(dbst, data);
    do {
	ret = bdb_test_error(st->dbcp->c_get(st->dbcp, &key, &data, st->sens));
	if (ret  == DB_NOTFOUND || ret == DB_KEYEMPTY)
	    return Qnil;
	rb_yield(bdb_assoc(st->db, &key, &data));
    } while (1);
    return Qnil;
}
 
static VALUE
bdb_join(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    bdb_DBC *dbcst;
    DBC *dbc, **dbcarr;
    int flags, i;
    eachst st;
    VALUE a, b, c;

    c = 0;
    flags = 0;
    GetDB(obj, dbst);
    if (rb_scan_args(argc, argv, "11", &a, &b) == 2) {
	flags = NUM2INT(b);
    }
    if (TYPE(a) != T_ARRAY) {
        rb_raise(bdb_eFatal, "first argument must an array of cursors");
    }
    if (RARRAY(a)->len == 0) {
        rb_raise(bdb_eFatal, "empty array");
    }
    dbcarr = ALLOCA_N(DBC *, RARRAY(a)->len + 1);
    {
	DBC **dbs;
	bdb_DB *tmp;

	for (dbs = dbcarr, i = 0; i < RARRAY(a)->len; i++, dbs++) {
	    if (!rb_obj_is_kind_of(RARRAY(a)->ptr[i], bdb_cCursor)) {
		rb_raise(bdb_eFatal, "element %d is not a cursor", i);
	    }
	    GetCursorDB(RARRAY(a)->ptr[i], dbcst, tmp);
	    *dbs = dbcst->dbc;
	}
	*dbs = 0;
    }
    dbc = 0;
#if BDB_VERSION < 30000
    bdb_test_error(dbst->dbp->join(dbst->dbp, dbcarr, 0, &dbc));
#else
    bdb_test_error(dbst->dbp->join(dbst->dbp, dbcarr, &dbc, 0));
#endif
    st.db = obj;
    st.dbcp = dbc;
    st.sens = flags | TEST_INIT_LOCK(dbst);
    rb_ensure(bdb_i_join, (VALUE)&st, bdb_i_joinclose, (VALUE)&st);
    return obj;
}

static VALUE
bdb_byteswapp(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    int byteswap = 0;
#if BDB_VERSION < 20500
    rb_raise(bdb_eFatal, "byteswapped needs Berkeley DB 2.5 or later");
#endif
    GetDB(obj, dbst);
#if BDB_VERSION < 30000
    return dbst->dbp->byteswapped?Qtrue:Qfalse;
#else
#if BDB_VERSION < 30300
    return dbst->dbp->get_byteswapped(dbst->dbp)?Qtrue:Qfalse;
#else
    dbst->dbp->get_byteswapped(dbst->dbp, &byteswap);
    return byteswap?Qtrue:Qfalse;
#endif
#endif
}

#endif

#if BDB_VERSION >= 30300

static VALUE
bdb_internal_second_call(tmp)
    VALUE *tmp;
{
    return rb_funcall2(tmp[0], bdb_id_call, 3, tmp + 1);
}

static int
bdb_call_secondary(secst, pkey, pdata, skey)
    DB *secst;
    DBT *pkey;
    DBT *pdata;
    DBT *skey;
{
    VALUE obj, ary, second;
    bdb_DB *dbst, *secondst;
    VALUE result = Qnil;
    int i, inter;

    GetIdDb(obj, dbst);
    if (!dbst->dbp || !RTEST(dbst->secondary)) return DB_DONOTINDEX;
    for (i = 0; i < RARRAY(dbst->secondary)->len; i++) {
	ary = RARRAY(dbst->secondary)->ptr[i];
	if (RARRAY(ary)->len != 2) continue;
	second = RARRAY(ary)->ptr[0];
	Data_Get_Struct(second, bdb_DB, secondst);
	if (!secondst->dbp) continue;
	if (secondst->dbp == secst) {
	    VALUE tmp[4];

	    tmp[0] = RARRAY(ary)->ptr[1];
	    tmp[1] = second;
	    tmp[2] = bdb_test_load_key(obj, pkey);
	    tmp[3] = bdb_test_load(obj, pdata, FILTER_VALUE|FILTER_FREE);
	    inter = 0;
	    result = rb_protect(bdb_internal_second_call, (VALUE)tmp, &inter);
	    if (inter) return BDB_ERROR_PRIVATE;
	    if (result == Qfalse) return DB_DONOTINDEX;
	    MEMZERO(skey, DBT, 1);
	    if (result == Qtrue) {
		skey->data = pkey->data;
		skey->size = pkey->size;
	    }
	    else {
		DBT stmp;
		MEMZERO(&stmp, DBT, 1);
		result = bdb_test_dump(second, &stmp, result, FILTER_KEY);
                skey->data = stmp.data;
		skey->size = stmp.size;
	    }
	    return 0;
	}
    }
    rb_gv_set("$!", rb_str_new2("secondary index not found ?"));
    return BDB_ERROR_PRIVATE;
}

static VALUE
bdb_associate(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst, *secondst;
    VALUE second, flag;
    int flags = 0;

    if (!rb_block_given_p()) {
	rb_raise(bdb_eFatal, "call out of an iterator");
    }
    if (rb_scan_args(argc, argv, "11", &second, &flag) == 2) {
	flags = NUM2INT(flag);
    }
    if (!rb_obj_is_kind_of(second, bdb_cCommon)) {
	rb_raise(bdb_eFatal, "associate expect a BDB object");
    }
    GetDB(second, secondst);
    if (RTEST(secondst->secondary)) {
	rb_raise(bdb_eFatal, "associate with a primary index");
    }
    GetDB(obj, dbst);

    dbst->options |= BDB_NEED_CURRENT;
    if (!dbst->secondary) {
	dbst->secondary = rb_ary_new();
    }
    rb_thread_local_aset(rb_thread_current(), bdb_id_current_db, obj);
#if HAVE_RB_BLOCK_PROC
    rb_ary_push(dbst->secondary, rb_assoc_new(second, rb_block_proc()));
#else
    rb_ary_push(dbst->secondary, rb_assoc_new(second, rb_f_lambda()));
#endif
    secondst->secondary = Qnil;

#if BDB_VERSION >= 40100
    {
	DB_TXN *txnid = NULL;
	if (RTEST(dbst->txn)) {
	    bdb_TXN *txnst;

	    GetTxnDB(dbst->txn, txnst);
	    txnid = txnst->txnid;
	}
	else if (dbst->options & BDB_AUTO_COMMIT) {
	  flags |= DB_AUTO_COMMIT;
	}
	bdb_test_error(dbst->dbp->associate(dbst->dbp, txnid, secondst->dbp, 
					    bdb_call_secondary, flags));
    }
#else
    bdb_test_error(dbst->dbp->associate(dbst->dbp, secondst->dbp, 
					bdb_call_secondary, flags));
#endif
    return obj;
}

#endif

static VALUE
bdb_filename(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    GetDB(obj, dbst);
    return dbst->filename;
}

static VALUE
bdb_database(obj)
    VALUE obj;
{
    bdb_DB *dbst;
    GetDB(obj, dbst);
    return dbst->database;
}

#if BDB_VERSION >= 30300
static VALUE
bdb_verify(int argc, VALUE *argv, VALUE obj)
{
    bdb_DB *dbst;
    char *file, *database;
    VALUE flagv = Qnil, iov = Qnil;
    int flags = 0;
    OpenFile *fptr;
    FILE *io = NULL;

    rb_secure(4);
    file = database = NULL;
    switch(rb_scan_args(argc, argv, "02", &iov, &flagv)) {
    case 2:
	flags = NUM2INT(flagv);
    case 1:
	if (!NIL_P(iov)) {
	    iov = rb_convert_type(iov, T_FILE, "IO", "to_io");
	    GetOpenFile(iov, fptr);
	    rb_io_check_writable(fptr);
#if HAVE_RB_IO_STDIO_FILE
	    io = rb_io_stdio_file(fptr);
#else
	    io = GetWriteFile(fptr);
#endif
	}
	break;
    case 0:
	break;
    }
    GetDB(obj, dbst);
    if (!NIL_P(dbst->filename)) {
	file = StringValuePtr(dbst->filename);
    }
    if (!NIL_P(dbst->database)) {
	database = StringValuePtr(dbst->database);
    }
    bdb_test_error(dbst->dbp->verify(dbst->dbp, file, database, io, flags));
    return Qnil;
}
#endif

static VALUE
bdb__txn__dup(VALUE obj, VALUE a)
{
    bdb_DB *dbp, *dbh;
    bdb_TXN *txnst;
    VALUE res;
 
    GetDB(obj, dbp);
    GetTxnDB(a, txnst);
    res = Data_Make_Struct(CLASS_OF(obj), bdb_DB, bdb_mark, bdb_free, dbh);
    MEMCPY(dbh, dbp, bdb_DB, 1);
    dbh->txn = a;
    dbh->orig = obj;
    dbh->ori_val = res;
    dbh->options |= (txnst->options & BDB_INIT_LOCK) | BDB_NOT_OPEN;
    return res;
}

static VALUE
bdb__txn__close(VALUE obj, VALUE commit, VALUE real)
{
    bdb_DB *dbst, *dbst1;

    if (!real) {
	Data_Get_Struct(obj, bdb_DB, dbst);
	dbst->dbp = NULL;
    }
    else {
	if (commit) {
	    Data_Get_Struct(obj, bdb_DB, dbst);
	    if (dbst->orig) {
		Data_Get_Struct(dbst->orig, bdb_DB, dbst1);
		dbst1->len = dbst->len;
	    }
	}
	bdb_close(0, 0, obj);
    }
    return Qnil;
}

#ifdef DB_PRIORITY_DEFAULT

static VALUE
bdb_cache_priority_set(VALUE obj, VALUE a)
{
    int priority;
    bdb_DB *dbst;
    GetDB(obj, dbst);
    priority = dbst->priority;
    bdb_test_error(dbst->dbp->set_cache_priority(dbst->dbp, NUM2INT(a)));
    dbst->priority = NUM2INT(a);
    return INT2FIX(priority);
}

static VALUE
bdb_cache_priority_get(VALUE obj)
{
    bdb_DB *dbst;
    GetDB(obj, dbst);
    return INT2FIX(dbst->priority);
}
#endif

#if BDB_VERSION >= 30000

static VALUE
bdb_feedback_set(VALUE obj, VALUE a)
{
    bdb_DB *dbst;

    GetDB(obj, dbst);
    if (NIL_P(a)) {
	dbst->feedback = a;
    }
    else {
	if (!rb_respond_to(a, bdb_id_call)) {
	    rb_raise(bdb_eFatal, "arg must respond to #call");
	}
	dbst->feedback = a;
	if (!(dbst->options & BDB_FEEDBACK)) {
	    dbst->options |= BDB_FEEDBACK;
	    rb_thread_local_aset(rb_thread_current(), bdb_id_current_db, obj); 
	}
    }
    return a;
}

#endif

#if BDB_VERSION >= 40250

static VALUE
bdb_i_conf(obj, a)
    VALUE obj, a;
{
    bdb_DB *dbst;
    u_int32_t bytes, gbytes, value;
    int intval, ncache;
    const char *str, *filename, *dbname, *strval;
    VALUE res;

    GetDB(obj, dbst);
    str = StringValuePtr(a);
    if (strcmp(str, "bt_minkey") == 0) {
	bdb_test_error(dbst->dbp->get_bt_minkey(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "cachesize") == 0) {
	bdb_test_error(dbst->dbp->get_cachesize(dbst->dbp, &gbytes, &bytes, &ncache));
	res = rb_ary_new2(3);
	rb_ary_push(res, INT2NUM(gbytes));
	rb_ary_push(res, INT2NUM(bytes));
	rb_ary_push(res, INT2NUM(ncache));
	return res;
    }
    if (strcmp(str, "dbname") == 0) {
	bdb_test_error(dbst->dbp->get_dbname(dbst->dbp, &filename, &dbname));
	res = rb_ary_new2(3);
	if (filename && strlen(filename)) {
	    rb_ary_push(res, rb_tainted_str_new2(filename));
	}
	else {
	    rb_ary_push(res, Qnil);
	}
	if (dbname && strlen(dbname)) {
	    rb_ary_push(res, rb_tainted_str_new2(dbname));
	}
	else {
	    rb_ary_push(res, Qnil);
	}
	return res;
    }
    if (strcmp(str, "env") == 0) {
	return bdb_env(obj);
    }
    if (strcmp(str, "h_ffactor") == 0) {
	bdb_test_error(dbst->dbp->get_h_ffactor(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "h_nelem") == 0) {
	bdb_test_error(dbst->dbp->get_h_nelem(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "lorder") == 0) {
	bdb_test_error(dbst->dbp->get_lorder(dbst->dbp, &intval));
	return INT2NUM(intval);
    }
    if (strcmp(str, "pagesize") == 0) {
	bdb_test_error(dbst->dbp->get_pagesize(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "q_extentsize") == 0) {
	bdb_test_error(dbst->dbp->get_q_extentsize(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "re_delim") == 0) {
	bdb_test_error(dbst->dbp->get_re_delim(dbst->dbp, &intval));
	return INT2NUM(intval);
    }
    if (strcmp(str, "re_len") == 0) {
	bdb_test_error(dbst->dbp->get_re_len(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "re_pad") == 0) {
	bdb_test_error(dbst->dbp->get_re_pad(dbst->dbp, &intval));
	return INT2NUM(intval);
    }
    if (strcmp(str, "re_source") == 0) {
	bdb_test_error(dbst->dbp->get_re_source(dbst->dbp, &strval));
	if (strval && strlen(strval)) {
	    return rb_tainted_str_new2(strval);
	}
	return Qnil;
    }
    if (strcmp(str, "flags") == 0) {
	bdb_test_error(dbst->dbp->get_flags(dbst->dbp, &value));
	return INT2NUM(value);
    }
    if (strcmp(str, "open_flags") == 0) {
	bdb_test_error(dbst->dbp->get_open_flags(dbst->dbp, &value));
	return INT2NUM(value);
    }
    rb_raise(rb_eArgError, "Unknown option %s", str);
    return obj;
}

static char *
options[] = {
    "bt_minkey", "cachesize", "dbname", "env", "h_ffactor", "h_nelem",
    "lorder", "pagesize", "q_extentsize", "re_delim", "re_len", "re_pad",
    "re_source", "flags", "open_flags", 0
};

struct optst {
    VALUE obj, str;
};

static VALUE
bdb_intern_conf(optp)
    struct optst *optp;
{
    return bdb_i_conf(optp->obj, optp->str);
}

static VALUE
bdb_conf(int argc, VALUE *argv, VALUE obj)
{
    int i, state;
    VALUE res, val;
    struct optst opt;

    if (argc > 1) {
	rb_raise(rb_eArgError, "invalid number of arguments (%d for 1)", argc);
    }
    if (argc == 1) {
	return bdb_i_conf(obj, argv[0]);
    }
    res = rb_hash_new();
    opt.obj = obj;
    for (i = 0; options[i] != NULL; i++) {
	opt.str = rb_str_new2(options[i]);
	val = rb_protect(bdb_intern_conf, (VALUE)&opt, &state);
	if (state == 0) {
	    rb_hash_aset(res, opt.str, val);
	}
    }
    return res;
}
	
#endif

void bdb_init_common()
{
    id_bt_compare = rb_intern("bdb_bt_compare");
    id_bt_prefix = rb_intern("bdb_bt_prefix");
    id_dup_compare = rb_intern("bdb_dup_compare");
    id_h_hash = rb_intern("bdb_h_hash");
#if BDB_VERSION >= 40100
    id_append_recno = rb_intern("bdb_append_recno");
#endif
#if BDB_VERSION >= 30000
    id_feedback = rb_intern("bdb_feedback");
#endif
    bdb_cCommon = rb_define_class_under(bdb_mDb, "Common", rb_cObject);
    rb_define_private_method(bdb_cCommon, "initialize", bdb_init, -1);
    rb_include_module(bdb_cCommon, rb_mEnumerable);
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    rb_define_alloc_func(bdb_cCommon, bdb_s_alloc);
#else
    rb_define_singleton_method(bdb_cCommon, "allocate", bdb_s_alloc, 0);
#endif
    rb_define_singleton_method(bdb_cCommon, "new", bdb_s_new, -1);
    rb_define_singleton_method(bdb_cCommon, "create", bdb_s_new, -1);
    rb_define_singleton_method(bdb_cCommon, "open", bdb_s_open, -1);
    rb_define_singleton_method(bdb_cCommon, "[]", bdb_s_create, -1);
    rb_define_singleton_method(bdb_cCommon, "remove", bdb_s_remove, -1);
    rb_define_singleton_method(bdb_cCommon, "bdb_remove", bdb_s_remove, -1);
    rb_define_singleton_method(bdb_cCommon, "unlink", bdb_s_remove, -1);
    rb_define_singleton_method(bdb_cCommon, "upgrade", bdb_s_upgrade, -1);
    rb_define_singleton_method(bdb_cCommon, "bdb_upgrade", bdb_s_upgrade, -1);
    rb_define_singleton_method(bdb_cCommon, "rename", bdb_s_rename, -1);
    rb_define_singleton_method(bdb_cCommon, "bdb_rename", bdb_s_rename, -1);
    rb_define_private_method(bdb_cCommon, "__txn_close__", bdb__txn__close, 2);
    rb_define_private_method(bdb_cCommon, "__txn_dup__", bdb__txn__dup, 1);
    rb_define_method(bdb_cCommon, "filename", bdb_filename, 0);
    rb_define_method(bdb_cCommon, "subname", bdb_database, 0);
    rb_define_method(bdb_cCommon, "database", bdb_database, 0);
#if BDB_VERSION >= 30300
    rb_define_method(bdb_cCommon, "verify", bdb_verify, -1);
#endif
    rb_define_method(bdb_cCommon, "close", bdb_close, -1);
    rb_define_method(bdb_cCommon, "db_close", bdb_close, -1);
    rb_define_method(bdb_cCommon, "put", bdb_put, -1);
    rb_define_method(bdb_cCommon, "db_put", bdb_put, -1);
    rb_define_method(bdb_cCommon, "[]=", bdb_aset, 2);
    rb_define_method(bdb_cCommon, "store", bdb_put, -1);
    rb_define_method(bdb_cCommon, "env", bdb_env, 0);
    rb_define_method(bdb_cCommon, "environment", bdb_env, 0);
    rb_define_method(bdb_cCommon, "has_env?", bdb_env_p, 0);
    rb_define_method(bdb_cCommon, "has_environment?", bdb_env_p, 0);
    rb_define_method(bdb_cCommon, "env?", bdb_env_p, 0);
    rb_define_method(bdb_cCommon, "environment?", bdb_env_p, 0);
    rb_define_method(bdb_cCommon, "txn", bdb_txn, 0);
    rb_define_method(bdb_cCommon, "transaction", bdb_txn, 0);
    rb_define_method(bdb_cCommon, "txn?", bdb_txn_p, 0);
    rb_define_method(bdb_cCommon, "transaction?", bdb_txn_p, 0);
    rb_define_method(bdb_cCommon, "in_txn?", bdb_txn_p, 0);
    rb_define_method(bdb_cCommon, "in_transaction?", bdb_txn_p, 0);
#if BDB_VERSION >= 20600
    rb_define_method(bdb_cCommon, "count", bdb_count, 1);
    rb_define_method(bdb_cCommon, "dup_count", bdb_count, 1);
    rb_define_method(bdb_cCommon, "each_dup", bdb_common_each_dup, -1);
    rb_define_method(bdb_cCommon, "each_dup_value", bdb_common_each_dup_val, -1);
    rb_define_method(bdb_cCommon, "dups", bdb_common_dups, -1);
    rb_define_method(bdb_cCommon, "duplicates", bdb_common_dups, -1);
    rb_define_method(bdb_cCommon, "get_dup", bdb_get_dup, -1);
#endif
    rb_define_method(bdb_cCommon, "get", bdb_get_dyna, -1);
    rb_define_method(bdb_cCommon, "db_get", bdb_get_dyna, -1);
    rb_define_method(bdb_cCommon, "[]", bdb_get_dyna, -1);
#if BDB_VERSION >= 30300
    rb_define_method(bdb_cCommon, "pget", bdb_pget, -1);
    rb_define_method(bdb_cCommon, "primary_get", bdb_pget, -1);
    rb_define_method(bdb_cCommon, "db_pget", bdb_pget, -1);
#endif
    rb_define_method(bdb_cCommon, "fetch", bdb_fetch, -1);
    rb_define_method(bdb_cCommon, "delete", bdb_del, 1);
    rb_define_method(bdb_cCommon, "del", bdb_del, 1);
    rb_define_method(bdb_cCommon, "db_del", bdb_del, 1);
    rb_define_method(bdb_cCommon, "sync", bdb_sync, 0);
    rb_define_method(bdb_cCommon, "db_sync", bdb_sync, 0);
    rb_define_method(bdb_cCommon, "flush", bdb_sync, 0);
    rb_define_method(bdb_cCommon, "each", bdb_each_pair, -1);
    rb_define_method(bdb_cCommon, "each_primary", bdb_each_pair_prim, -1);
    rb_define_method(bdb_cCommon, "each_value", bdb_each_value, -1);
    rb_define_method(bdb_cCommon, "reverse_each_value", bdb_each_eulav, -1);
    rb_define_method(bdb_cCommon, "each_key", bdb_each_key, -1);
    rb_define_method(bdb_cCommon, "reverse_each_key", bdb_each_yek, -1);
    rb_define_method(bdb_cCommon, "each_pair", bdb_each_pair, -1);
    rb_define_method(bdb_cCommon, "reverse_each", bdb_each_riap, -1);
    rb_define_method(bdb_cCommon, "reverse_each_pair", bdb_each_riap, -1);
    rb_define_method(bdb_cCommon, "reverse_each_primary", bdb_each_riap_prim, -1);
    rb_define_method(bdb_cCommon, "keys", bdb_keys, 0);
    rb_define_method(bdb_cCommon, "values", bdb_values, 0);
    rb_define_method(bdb_cCommon, "delete_if", bdb_delete_if, -1);
    rb_define_method(bdb_cCommon, "reject!", bdb_delete_if, -1);
    rb_define_method(bdb_cCommon, "reject", bdb_reject, -1);
    rb_define_method(bdb_cCommon, "clear", bdb_clear, -1);
    rb_define_method(bdb_cCommon, "truncate", bdb_clear, -1);
    rb_define_method(bdb_cCommon, "replace", bdb_replace, 1);
    rb_define_method(bdb_cCommon, "update", bdb_update, 1);
    rb_define_method(bdb_cCommon, "include?", bdb_has_key, 1);
    rb_define_method(bdb_cCommon, "has_key?", bdb_has_key, 1);
    rb_define_method(bdb_cCommon, "key?", bdb_has_key, 1);
    rb_define_method(bdb_cCommon, "member?", bdb_has_key, 1);
    rb_define_method(bdb_cCommon, "has_value?", bdb_has_value, 1);
    rb_define_method(bdb_cCommon, "value?", bdb_has_value, 1);
    rb_define_method(bdb_cCommon, "has_both?", bdb_has_both, 2);
    rb_define_method(bdb_cCommon, "both?", bdb_has_both, 2);
    rb_define_method(bdb_cCommon, "to_a", bdb_to_a, 0);
    rb_define_method(bdb_cCommon, "to_hash", bdb_to_hash, 0);
    rb_define_method(bdb_cCommon, "invert", bdb_invert, 0);
    rb_define_method(bdb_cCommon, "empty?", bdb_empty, 0);
    rb_define_method(bdb_cCommon, "length", bdb_length, 0);
    rb_define_alias(bdb_cCommon,  "size", "length");
    rb_define_method(bdb_cCommon, "index", bdb_index, 1);
    rb_define_method(bdb_cCommon, "indexes", bdb_indexes, -1);
    rb_define_method(bdb_cCommon, "indices", bdb_indexes, -1);
    rb_define_method(bdb_cCommon, "select", bdb_select, -1);
    rb_define_method(bdb_cCommon, "values_at", bdb_values_at, -1);
    rb_define_method(bdb_cCommon, "set_partial", bdb_set_partial, 2);
    rb_define_method(bdb_cCommon, "clear_partial", bdb_clear_partial, 0);
    rb_define_method(bdb_cCommon, "partial_clear", bdb_clear_partial, 0);
#if BDB_VERSION >= 20600
    rb_define_method(bdb_cCommon, "join", bdb_join, -1);
    rb_define_method(bdb_cCommon, "byteswapped?", bdb_byteswapp, 0);
    rb_define_method(bdb_cCommon, "get_byteswapped", bdb_byteswapp, 0);
#endif
#if BDB_VERSION >= 30300
    rb_define_method(bdb_cCommon, "associate", bdb_associate, -1);
#endif
#ifdef DB_PRIORITY_DEFAULT
    rb_define_method(bdb_cCommon, "cache_priority=", bdb_cache_priority_set, 1);
    rb_define_method(bdb_cCommon, "cache_priority", bdb_cache_priority_get, 0);
#endif
#if BDB_VERSION >= 30000
    rb_define_method(bdb_cCommon, "feedback=", bdb_feedback_set, 1);
#endif
    bdb_cBtree = rb_define_class_under(bdb_mDb, "Btree", bdb_cCommon);
    rb_define_method(bdb_cBtree, "stat", bdb_tree_stat, -1);
    rb_define_method(bdb_cBtree, "each_by_prefix", bdb_each_prefix, -1);
    rb_define_method(bdb_cBtree, "reverse_each_by_prefix", bdb_each_xiferp, -1);
#if BDB_VERSION >= 40416
    rb_define_method(bdb_cBtree, "compact", bdb_treerec_compact, -1);
#endif
#if BDB_VERSION >= 30100
    bdb_sKeyrange = rb_struct_define("Keyrange", "less", "equal", "greater", 0);
    rb_global_variable(&bdb_sKeyrange);
    rb_define_method(bdb_cBtree, "key_range", bdb_btree_key_range, 1);
#endif
    bdb_cHash = rb_define_class_under(bdb_mDb, "Hash", bdb_cCommon);
#if BDB_VERSION >= 30000
    rb_define_method(bdb_cHash, "stat", bdb_hash_stat, -1);
#endif
    bdb_cRecno = rb_define_class_under(bdb_mDb, "Recno", bdb_cCommon);
    rb_define_method(bdb_cRecno, "each_index", bdb_each_key, -1);
    rb_define_method(bdb_cRecno, "unshift", bdb_unshift, -1);
    rb_define_method(bdb_cRecno, "<<", bdb_append, 1);
    rb_define_method(bdb_cRecno, "push", bdb_append_m, -1);
    rb_define_method(bdb_cRecno, "stat", bdb_tree_stat, -1);
#if BDB_VERSION >= 40416
    rb_define_method(bdb_cRecno, "compact", bdb_treerec_compact, -1);
#endif
#if BDB_VERSION >= 30000
    bdb_cQueue = rb_define_class_under(bdb_mDb, "Queue", bdb_cCommon);
    rb_define_singleton_method(bdb_cQueue, "new", bdb_queue_s_new, -1);
    rb_define_singleton_method(bdb_cQueue, "create", bdb_queue_s_new, -1);
    rb_define_method(bdb_cQueue, "each_index", bdb_each_key, -1);
    rb_define_method(bdb_cQueue, "<<", bdb_append, 1);
    rb_define_method(bdb_cQueue, "push", bdb_append_m, -1);
    rb_define_method(bdb_cQueue, "shift", bdb_consume, 0);
    rb_define_method(bdb_cQueue, "stat", bdb_queue_stat, -1);
    rb_define_method(bdb_cQueue, "pad", bdb_queue_padlen, 0);
#endif
#if BDB_VERSION >= 40250
    rb_define_method(bdb_cCommon, "configuration", bdb_conf, -1);
    rb_define_method(bdb_cCommon, "conf", bdb_conf, -1);
#endif
    bdb_cUnknown = rb_define_class_under(bdb_mDb, "Unknown", bdb_cCommon);
}
