Initial revision

This commit is contained in:
danj 2006-02-14 02:56:36 +00:00
commit 20a230483c
7 changed files with 1173 additions and 0 deletions

2
MANIFEST Normal file
View file

@ -0,0 +1,2 @@
bdb2.c
rusage.c

72
README Normal file
View file

@ -0,0 +1,72 @@
This interface is most closely based on the DB4 C api and tries to
maintain close interface proximity.
That API is published by sleepycat at
http://www.sleepycat.com/docs/api_c/frame.html
function all arguments that are systematically omitted are leading
DB handles and TXN handles. A few calls omit the flags parameter when the
documenation indicates that no flag values are used. cursor.close is one.
the defines generator is imperfect and includes some defines that are not
flags. while it could be improved, it is easier to delete the incorrect ones.
thus, if you decide to rebuild the defines, you will need to edit the resulting
file. this may be necessary if using a different release of DB4 than the one
I used.
I have put all possible caution into ensuring that DB and Ruby cooperate.
The memory access was one apsect carefully considered. Since Ruby copies
when doing String#new, all key/data retrieval from DB is done with a 0 flag,
meaning that DB will be responsible. See the copied news group posting about
the effect of that.
The only other design consideration of consequence was associate. The prior
version used a Ruby thread local variable and kept track of the current
database in use. I decided to take a simpler approach since Ruby is green
threads. A global array stores the VALUE of the Proc for a given association
by the file descriptor number of the underlying database. This is looked
up when the first layer callback is made. It would have been better considered
if DB allowed the passing of a (void *) user data into the alloc that would
be supplied during callback. So far this design has not produced any problems.
[This is a message from comp.databases.berkeley-db]
http://groups.google.com/group/comp.databases.berkeley-db/browse_frm/thread/4f70a9999b64ce6a/c06b94692e3cbc41?tvc=1&q=dbt+malloc#c06b94692e3cbc41
Subject: Some questions about BerkeleyDB
Patrick Schaaf
Sep 16 2004, 9:31 am show options
Hi Cylin,
I'm only replying to one point; I'm not so sure about the others.
>> >4.In http://www.sleepycat.com/docs/api_c/dbt_class.html#DB_DBT_MALLOC
>I mean when we query by a key, and get return data( or key).
>If we set DB_DBT_MALLOC or not ,what is the difference about
>data.data/key.data?
DB_DBT_MALLOC is only relevant for the DBT in which Berkeley DB
gives you back data from the database.
Without DB_DBT_MALLOC, i.e. with DBT.flags set to 0, after the successful
query call, you will find in data.data a pointer into memory which is
under the responsibility of Berkeley DB. It may be (I don't know for sure)
a pointer into the memory mapped shared environment. You can copy from
there to some place safe, and I think you must NOT assume that the pointer
will be valid after another call to a retrieval function.
On the other hand, when you set (before the retrieval call) that DBT's
.flags field to DB_DBT_MALLOC, then the retrieval function of Berkeley DB
will automatically call malloc() to get _new_ memory for the retrieved
data, and you will find after the retrieval that data.data points
to that memory. As it is newly malloc()ed, you can access it for as
long as you want. IMPORTANT: it is also your responsibility to
use free() when you don't need it any more.
best regards
Patrick

809
bdb.c Normal file
View file

@ -0,0 +1,809 @@
#include <bdb.h>
#include <stdio.h>
#define LMEMFLAG 0
#ifdef HAVE_STDARG_PROTOTYPES
#include <stdarg.h>
#define va_init_list(a,b) va_start(a,b)
#else
#include <varargs.h>
#define va_init_list(a,b) va_start(a)
#endif
VALUE mBdb; /* Top level module */
VALUE cDb; /* DBT class */
VALUE cEnv; /* Environment class */
VALUE cTxn; /* Transaction class */
VALUE cCursor; /* Cursors */
VALUE eDbError;
static ID fv_txn, fv_call, fv_err_new,fv_err_code,fv_err_msg;
static void
#ifdef HAVE_STDARG_PROTOTYPES
raise_error(int code, const char *fmt, ...)
#else
raise_error(code,fmt,va_alist)
int code;
const char *fmt;
va_dcl
#endif
{
va_list args;
char buf[1024];
VALUE exc;
VALUE argv[2];
va_init_list(args,fmt);
vsnprintf(buf,1024,fmt,args);
va_end(args);
argv[0]=rb_str_new2(buf);
argv[1]=INT2NUM(code);
exc=rb_class_new_instance(2,argv,eDbError);
rb_exc_raise(exc);
}
VALUE err_initialize(VALUE obj, VALUE message, VALUE code)
{
VALUE args[1];
args[0]=message;
rb_call_super(1,args);
rb_ivar_set(obj,fv_err_code,code);
}
VALUE err_code(VALUE obj)
{
return rb_ivar_get(obj,fv_err_code);
}
static void db_free(void *p)
{
t_dbh *dbh;
dbh=(t_dbh *)p;
#ifdef DEBUG_DB
if ( RTEST(ruby_debug) )
fprintf(stderr,"%s/%d %s 0x%x\n",__FILE__,__LINE__,"db_free cleanup!",p);
#endif
if ( dbh ) {
if ( dbh->dbp ) {
dbh->dbp->close(dbh->dbp,0);
}
if ( dbh->aproc ) {
rb_gc_unregister_address(&(dbh->aproc));
}
free(p);
}
}
static void db_mark(void *p)
{
t_dbh *dbh;
if ( dbh->aproc )
rb_gc_mark(dbh->aproc);
}
static void dbc_free(void *p)
{
t_dbch *dbch;
dbch=(t_dbch *)p;
#ifdef DEBUG_DB
if ( RTEST(ruby_debug) )
fprintf(stderr,"%s/%d %s 0x%x\n",__FILE__,__LINE__,
"dbc_free cleanup!",p);
#endif
if ( dbch ) {
if ( dbch->dbc ) {
dbch->dbc->c_close(dbch->dbc);
if ( RTEST(ruby_debug) )
fprintf(stderr,"%s/%d %s 0x%x %s\n",__FILE__,__LINE__,
"dbc_free cursor was still open!",p,dbch->filename);
}
free(p);
}
}
VALUE
db_alloc(VALUE klass)
{
return Data_Wrap_Struct(klass,0,db_free,0);
}
VALUE db_init_aux(VALUE obj,DB_ENV *env)
{
DB *db;
t_dbh *dbh;
int rv;
rv = db_create(&db,env,0);
if (rv != 0) {
raise_error(rv, "db_new failure: %s",db_strerror(rv));
}
/*
if ( env == NULL )
dbh->dbp->set_alloc(dbh->dbp,(void *(*)(size_t))xmalloc,
(void *(*)(void *,size_t))xrealloc,
xfree);
*/
#ifdef DEBUG_DB
dbh->dbp->set_errfile(dbh->dbp,stderr);
#endif
rb_ivar_set(obj,fv_txn,Qnil);
dbh=ALLOC(t_dbh);
db_free(DATA_PTR(obj));
DATA_PTR(obj)=dbh;
dbh->dbp=db;
dbh->dbinst=obj;
return obj;
}
VALUE db_initialize(VALUE obj)
{
return db_init_aux(obj,NULL);
}
VALUE db_open(VALUE obj, VALUE vdisk_file, VALUE vlogical_db,
VALUE vdbtype, VALUE vflags, VALUE vmode)
{
t_dbh *dbh;
int rv;
VALUE vtxn;
DB_TXN *txn=NULL;
u_int32_t flags=0;
DBTYPE dbtype=DB_UNKNOWN;
char *logical_db=NULL;
long len;
int mode=0;
vtxn=rb_ivar_get(obj,fv_txn);
if ( ! NIL_P(vflags) )
flags=NUM2INT(vflags);
if ( ! NIL_P(vtxn) )
txn=NOTXN; /* XXX when implemented */
if ( TYPE(vlogical_db)==T_STRING && RSTRING(vlogical_db)->len > 0 )
logical_db=StringValueCStr(vlogical_db);
if ( FIXNUM_P(vdbtype) ) {
dbtype=NUM2INT(vdbtype);
if ( dbtype < DB_BTREE || dbtype > DB_UNKNOWN ) {
raise_error(0,"db_open Bad access type: %d",dbtype);
return Qnil;
}
}
if ( TYPE(vdisk_file)!=T_STRING || RSTRING(vdisk_file)->len < 1 ) {
raise_error(0,"db_open Bad disk file name");
return Qnil;
}
if ( ! NIL_P(vmode) )
mode=NUM2INT(vmode);
Data_Get_Struct(obj,t_dbh,dbh);
rv = dbh->dbp->open(dbh->dbp,txn,StringValueCStr(vdisk_file),logical_db,
dbtype,flags,mode);
if (rv != 0) {
raise_error(rv,"db_open failure: %s",db_strerror(rv));
}
filename_copy(dbh->filename,vdisk_file)
return obj;
}
VALUE db_flags_set(VALUE obj, VALUE vflags)
{
t_dbh *dbh;
int rv;
u_int32_t flags;
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,dbh);
rv = dbh->dbp->set_flags(dbh->dbp,flags);
if ( rv != 0 ) {
raise_error(rv, "db_flag_set failure: %s",db_strerror(rv));
}
return vflags;
}
VALUE db_close(VALUE obj, VALUE vflags)
{
t_dbh *dbh;
int rv;
u_int32_t flags;
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,dbh);
if ( RTEST(ruby_debug) )
rb_warning("%s/%d %s 0x%x %s",__FILE__,__LINE__,"db_close!",dbh,
dbh->filename);
rv = dbh->dbp->close(dbh->dbp,flags);
if ( rv != 0 ) {
raise_error(rv, "db_close failure: %s",db_strerror(rv));
}
dbh->dbp=NULL;
dbh->aproc=(VALUE)NULL;
return obj;
}
VALUE db_put(VALUE obj, VALUE vkey, VALUE vdata, VALUE vflags)
{
t_dbh *dbh;
int rv;
u_int32_t flags=0;
DBT key,data;
memset(&key,0,sizeof(DBT));
memset(&data,0,sizeof(DBT));
if ( ! NIL_P(vflags) )
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,dbh);
StringValue(vkey);
key.data = RSTRING(vkey)->ptr;
key.size = RSTRING(vkey)->len;
key.flags = LMEMFLAG;
StringValue(vdata);
data.data = RSTRING(vdata)->ptr;
data.size = RSTRING(vdata)->len;
data.flags = LMEMFLAG;
rv = dbh->dbp->put(dbh->dbp,NULL,&key,&data,flags);
/*
if (rv == DB_KEYEXIST)
return Qnil;
*/
if (rv != 0) {
raise_error(rv, "db_put fails: %s",db_strerror(rv));
}
return obj;
}
VALUE db_get(VALUE obj, VALUE vkey, VALUE vdata, VALUE vflags)
{
t_dbh *dbh;
int rv;
u_int32_t flags=0;
DBT key,data;
VALUE str;
memset(&key,0,sizeof(DBT));
memset(&data,0,sizeof(DBT));
if ( ! NIL_P(vflags) ) {
rb_warning("flags nil");
flags=NUM2INT(vflags);
}
Data_Get_Struct(obj,t_dbh,dbh);
StringValue(vkey);
key.data = RSTRING(vkey)->ptr;
key.size = RSTRING(vkey)->len;
key.flags = LMEMFLAG;
if ( ! NIL_P(vdata) ) {
StringValue(vdata);
data.data = RSTRING(vdata)->ptr;
data.size = RSTRING(vdata)->len;
data.flags = LMEMFLAG;
}
rv = dbh->dbp->get(dbh->dbp,NULL,&key,&data,flags);
if ( rv == 0 ) {
return rb_str_new(data.data,data.size);
} else if (rv == DB_NOTFOUND) {
return Qnil;
} else {
raise_error(rv, "db_get failure: %s",db_strerror(rv));
}
return Qnil;
}
VALUE db_aget(VALUE obj, VALUE vkey)
{
return db_get(obj,vkey,Qnil,Qnil);
}
VALUE db_aset(VALUE obj, VALUE vkey, VALUE vdata)
{
return db_put(obj,vkey,vdata,Qnil);
}
VALUE db_join(VALUE obj, VALUE vacurs, VALUE vflags)
{
t_dbh *dbh;
t_dbch *dbch;
int flags;
DBC **curs;
int i,rv;
VALUE jcurs;
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,dbh);
curs = ALLOCA_N(DBC *,RARRAY(vacurs)->len);
for (i=0; i<RARRAY(vacurs)->len; i++) {
Data_Get_Struct(RARRAY(vacurs)->ptr[i],t_dbch,dbch);
curs[i]=dbch->dbc;
}
curs[i]=NULL;
jcurs=Data_Make_Struct(cCursor,t_dbch,0,dbc_free,dbch);
rv = dbh->dbp->join(dbh->dbp,curs,&(dbch->dbc),flags);
if (rv) {
raise_error(rv, "db_join: %s",db_strerror(rv));
}
rb_obj_call_init(jcurs,0,NULL);
return jcurs;
}
VALUE db_del(VALUE obj, VALUE vkey, VALUE vflags)
{
t_dbh *dbh;
int rv;
u_int32_t flags;
DBT key;
VALUE str;
memset(&key,0,sizeof(DBT));
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,dbh);
StringValue(vkey);
key.data = RSTRING(vkey)->ptr;
key.size = RSTRING(vkey)->len;
key.flags = LMEMFLAG;
rv = dbh->dbp->del(dbh->dbp,NOTXN,&key,flags);
if ( rv == DB_NOTFOUND ) {
return Qnil;
} else if (rv != 0) {
raise_error(rv, "db_del failure: %s",db_strerror(rv));
}
return Qtrue;
}
t_dbh *dbassoc[OPEN_MAX];
VALUE
assoc_callback2(VALUE *args)
{
rb_funcall(args[0],fv_call,3,args[1],args[2],args[3]);
}
int assoc_callback(DB *secdb,const DBT* key, const DBT* data, DBT* result)
{
t_dbh *dbh;
VALUE proc;
int fdp,status;
VALUE retv;
VALUE args[4];
memset(result,0,sizeof(DBT));
secdb->fd(secdb,&fdp);
dbh=dbassoc[fdp];
args[0]=dbh->aproc;
args[1]=dbh->dbinst;
args[2]=rb_str_new(key->data,key->size);
args[3]=rb_str_new(data->data,data->size);
retv=rb_protect((VALUE(*)_((VALUE)))assoc_callback2,(VALUE)args,&status);
if (status) return 99999;
if ( NIL_P(retv) )
return DB_DONOTINDEX;
if ( TYPE(retv) != T_STRING )
rb_warning("return from assoc callback not a string!");
StringValue(retv);
result->data=RSTRING(retv)->ptr;
result->size=RSTRING(retv)->len;
result->flags=LMEMFLAG;
return 0;
}
VALUE db_associate(VALUE obj, VALUE osecdb, VALUE vflags, VALUE cb_proc)
{
t_dbh *sdbh,*pdbh;
int rv;
u_int32_t flags,flagsp,flagss;
int fdp;
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,pdbh);
Data_Get_Struct(osecdb,t_dbh,sdbh);
if ( cb_proc == Qnil ) {
rb_warning("db_associate: no association may be applied");
pdbh->dbp->get_open_flags(pdbh->dbp,&flagsp);
sdbh->dbp->get_open_flags(sdbh->dbp,&flagss);
if ( flagsp & DB_RDONLY & flagss ) {
rv=pdbh->dbp->associate(pdbh->dbp,NOTXN,sdbh->dbp,NULL,flags);
if (rv)
raise_error(rv,"db_associate: %s",db_strerror(rv));
} else {
raise_error(0,"db_associate empty associate only available when both DBs opened with DB_RDONLY");
}
} else if ( rb_obj_is_instance_of(cb_proc,rb_cProc) != Qtrue ) {
raise_error(0, "db_associate proc required");
}
sdbh->dbp->fd(sdbh->dbp,&fdp);
sdbh->aproc=cb_proc;
/*
* I do not think this is necessary since GC knows about sdbh and its
* size, so it will find this address, I think.
*/
#ifdef DEBUG_DB
fprintf(stderr,"registering 0x%x 0x%x\n",&(sdbh->aproc),sdbh->aproc);
#endif
rb_gc_register_address(&(sdbh->aproc));
dbassoc[fdp]=sdbh;
rv=pdbh->dbp->associate(pdbh->dbp,NOTXN,sdbh->dbp,assoc_callback,flags);
#ifdef DEBUG_DB
fprintf(stderr,"file is %d\n",fdp);
fprintf(stderr,"assoc done 0x%x 0x%x\n",sdbh,dbassoc[fdp]);
#endif
if (rv != 0) {
raise_error(rv, "db_associate failure: %s",db_strerror(rv));
}
return Qtrue;
}
VALUE db_cursor(VALUE obj, VALUE vflags)
{
t_dbh *dbh;
int rv;
u_int32_t flags;
DBC *dbc;
VALUE c_obj;
t_dbch *dbch;
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbh,dbh);
c_obj=Data_Make_Struct(cCursor, t_dbch, 0, dbc_free, dbch);
rv=dbh->dbp->cursor(dbh->dbp,NOTXN,&(dbch->dbc),flags);
if (rv)
raise_error(rv,"db_cursor: %s",db_strerror(rv));
filename_dup(dbch->filename,dbh->filename);
rb_obj_call_init(c_obj,0,NULL);
return c_obj;
}
VALUE dbc_close(VALUE obj)
{
t_dbch *dbch;
int rv;
Data_Get_Struct(obj,t_dbch,dbch);
if ( dbch->dbc ) {
rv=dbch->dbc->c_close(dbch->dbc);
if (rv)
raise_error(rv,"dbc_close: %s",db_strerror(rv));
dbch->dbc=NULL;
}
return Qnil;
}
VALUE dbc_get(VALUE obj, VALUE vkey, VALUE vdata, VALUE vflags)
{
t_dbch *dbch;
u_int32_t flags;
DBT key,data;
VALUE rar;
int rv;
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbch,dbch);
memset(&key,0,sizeof(DBT));
memset(&data,0,sizeof(DBT));
if ( ! NIL_P(vkey) ) {
StringValue(vkey);
key.data = RSTRING(vkey)->ptr;
key.size = RSTRING(vkey)->len;
key.flags = LMEMFLAG;
}
if ( ! NIL_P(vdata) ) {
StringValue(vdata);
data.data = RSTRING(vdata)->ptr;
data.size = RSTRING(vdata)->len;
data.flags = LMEMFLAG;
}
rv = dbch->dbc->c_get(dbch->dbc,&key,&data,flags);
if ( rv == 0 ) {
rar = rb_ary_new3(2,rb_str_new(key.data,key.size),
rb_str_new(data.data,data.size));
return rar;
} else if (rv == DB_NOTFOUND) {
return Qnil;
} else {
raise_error(rv, "dbc_get %s",db_strerror(rv));
}
return Qnil;
}
VALUE dbc_put(VALUE obj, VALUE vkey, VALUE vdata, VALUE vflags)
{
t_dbch *dbch;
u_int32_t flags;
DBT key,data;
int rv;
if ( NIL_P(vdata) )
raise_error(0,"data element is required for put");
flags=NUM2INT(vflags);
Data_Get_Struct(obj,t_dbch,dbch);
memset(&key,0,sizeof(DBT));
memset(&data,0,sizeof(DBT));
if ( ! NIL_P(vkey) ) {
StringValue(vkey);
key.data = RSTRING(vkey)->ptr;
key.size = RSTRING(vkey)->len;
key.flags = LMEMFLAG;
}
StringValue(vdata);
data.data = RSTRING(vdata)->ptr;
data.size = RSTRING(vdata)->len;
data.flags = LMEMFLAG;
rv = dbch->dbc->c_put(dbch->dbc,&key,&data,flags);
if (rv != 0)
raise_error(rv,"dbc_put failure: %s",db_strerror(rv));
return vdata;
}
VALUE dbc_del(VALUE obj)
{
t_dbch *dbch;
int rv;
Data_Get_Struct(obj,t_dbch,dbch);
rv = dbch->dbc->c_del(dbch->dbc,0);
if (rv == DB_KEYEMPTY)
return Qnil;
else if (rv != 0) {
raise_error(rv, "dbc_del failure: %s",db_strerror(rv));
}
return Qtrue;
}
VALUE dbc_count(VALUE obj)
{
t_dbch *dbch;
int rv;
db_recno_t count;
Data_Get_Struct(obj,t_dbch,dbch);
rv = dbch->dbc->c_count(dbch->dbc,&count,0);
if (rv != 0)
raise_error(rv, "db_count failure: %s",db_strerror(rv));
return INT2NUM(count);
}
static void env_free(void *p)
{
t_envh *eh;
eh=(t_envh *)p;
#ifdef DEBUG_DB
if ( RTEST(ruby_debug) )
fprintf(stderr,"%s/%d %s 0x%x\n",__FILE__,__LINE__,"env_free cleanup!",p);
#endif
if ( eh ) {
if ( eh->env ) {
eh->env->close(eh->env,0);
}
free(p);
}
}
VALUE env_new(VALUE class)
{
t_envh *eh;
int rv;
VALUE obj;
obj=Data_Make_Struct(class,t_envh,NULL,env_free,eh);
rv=db_env_create(&(eh->env),0);
if ( rv != 0 ) {
raise_error(rv,"env_new: %s",db_strerror(rv));
return Qnil;
}
/*
eh->env->set_alloc(eh->env,(void *(*)(size_t))xmalloc,
(void *(*)(void *,size_t))xrealloc,
xfree);
*/
rb_ivar_set(obj,fv_txn,Qnil);
rb_obj_call_init(obj,0,NULL);
return obj;
}
VALUE env_open(VALUE obj, VALUE vhome, VALUE vflags, VALUE vmode)
{
t_envh *eh;
int rv;
u_int32_t flags=0;
int mode=0;
if ( ! NIL_P(vflags) )
flags=NUM2INT(vflags);
if ( ! NIL_P(vmode) )
mode=NUM2INT(vmode);
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->open(eh->env,StringValueCStr(vhome),flags,mode);
if (rv != 0) {
raise_error(rv, "env_open failure: %s",db_strerror(rv));
}
return obj;
}
VALUE env_close(VALUE obj)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
if ( RTEST(ruby_debug) )
rb_warning("%s/%d %s 0x%x",__FILE__,__LINE__,"env_close!",eh);
rv = eh->env->close(eh->env,0);
if ( rv != 0 ) {
raise_error(rv, "env_close failure: %s",db_strerror(rv));
return Qnil;
}
eh->env=NULL;
return obj;
}
VALUE env_db(VALUE obj)
{
t_envh *eh;
VALUE dbo;
Data_Get_Struct(obj,t_envh,eh);
dbo = Data_Wrap_Struct(cDb,0,db_free,0);
return db_init_aux(dbo,eh->env);
}
VALUE env_set_cachesize(VALUE obj, VALUE size)
{
t_envh *eh;
unsigned long long ln;
u_int32_t bytes=0,gbytes=0;
int rv;
Data_Get_Struct(obj,t_envh,eh);
if ( TYPE(size) == T_BIGNUM ) {
ln = rb_big2ull(size);
bytes = (u_int32_t) ln;
gbytes = (u_int32_t) (ln >> sizeof(u_int32_t));
} else if (FIXNUM_P(size) ) {
bytes=NUM2INT(size);
} else {
raise_error(0,"set_cachesize requires number");
return Qnil;
}
rb_warning("setting cache size %d %d %d",gbytes,bytes,1);
rv=eh->env->set_cachesize(eh->env,gbytes,bytes,1);
if ( rv != 0 ) {
raise_error(rv, "set_cachesize failure: %s",db_strerror(rv));
return Qnil;
}
return Qtrue;
}
VALUE env_set_flags(VALUE obj, VALUE vflags, int onoff)
{
t_envh *eh;
int rv;
u_int32_t flags;
Data_Get_Struct(obj,t_envh,eh);
if ( ! NIL_P(vflags) ) {
flags=NUM2INT(vflags);
rv=eh->env->set_flags(eh->env,flags,onoff);
if ( rv != 0 ) {
raise_error(rv, "set_flags failure: %s",db_strerror(rv));
return Qnil;
}
}
return Qtrue;
}
VALUE env_set_flags_on(VALUE obj, VALUE vflags)
{
return env_set_flags(obj,vflags,1);
}
VALUE env_set_flags_off(VALUE obj, VALUE vflags)
{
return env_set_flags(obj,vflags,0);
}
simple_set(txn);
void Init_bdb2() {
fv_txn=rb_intern("@txn");
fv_call=rb_intern("call");
fv_err_new=rb_intern("new");
fv_err_code=rb_intern("@code");
fv_err_msg=rb_intern("@message");
mBdb = rb_define_module("Bdb");
#include "bdb_aux._c"
cDb = rb_define_class_under(mBdb,"Db", rb_cObject);
eDbError = rb_define_class_under(mBdb,"DbError",rb_eStandardError);
rb_define_method(eDbError,"initialize",err_initialize,2);
rb_define_method(eDbError,"code",err_code,0);
rb_define_const(cDb,"BTREE",INT2FIX((DBTYPE)(DB_BTREE)));
rb_define_const(cDb,"HASH",INT2FIX((DBTYPE)(DB_HASH)));
rb_define_const(cDb,"RECNO",INT2FIX((DBTYPE)(DB_RECNO)));
rb_define_const(cDb,"QUEUE",INT2FIX((DBTYPE)(DB_QUEUE)));
rb_define_const(cDb,"UNKNOWN",INT2FIX((DBTYPE)(DB_UNKNOWN)));
rb_define_alloc_func(cDb,db_alloc);
rb_define_method(cDb,"initialize",db_initialize,0);
rb_define_method(cDb,"txn=",db_txn_eq,1);
rb_define_method(cDb,"put",db_put,3);
rb_define_method(cDb,"get",db_get,3);
rb_define_method(cDb,"del",db_del,2);
rb_define_method(cDb,"cursor",db_cursor,1);
rb_define_method(cDb,"associate",db_associate,3);
rb_define_method(cDb,"flags=",db_flags_set,1);
rb_define_method(cDb,"open",db_open,5);
rb_define_method(cDb,"close",db_close,1);
rb_define_method(cDb,"[]",db_aget,1);
rb_define_method(cDb,"[]=",db_aset,2);
rb_define_method(cDb,"join",db_join,2);
cCursor = rb_define_class_under(cDb,"Cursor",rb_cObject);
rb_define_method(cCursor,"get",dbc_get,3);
rb_define_method(cCursor,"put",dbc_put,3);
rb_define_method(cCursor,"close",dbc_close,0);
rb_define_method(cCursor,"del",dbc_del,0);
rb_define_method(cCursor,"count",dbc_count,0);
cEnv = rb_define_class_under(mBdb,"Env",rb_cObject);
rb_define_singleton_method(cEnv,"new",env_new,0);
rb_define_method(cEnv,"open",env_open,3);
rb_define_method(cEnv,"close",env_close,0);
rb_define_method(cEnv,"db",env_db,0);
rb_define_method(cEnv,"cachesize=",env_set_cachesize,1);
rb_define_method(cEnv,"flags_on=",env_set_flags_on,1);
rb_define_method(cEnv,"flags_off=",env_set_flags_off,1);
}

49
bdb.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef BDB2_H
#define BDB2_H
#include <ruby.h>
#include <version.h>
#include <db4/db.h>
#define NOTXN NULL
#define FNLEN 40
#define filename_copy(fp,fv) \
strncpy(fp,RSTRING(fv)->ptr,FNLEN);
#define filename_dup(fpd,fps) \
strncpy(fpd,fps,FNLEN);
typedef struct s_dbh {
VALUE dbinst;
DB *dbp;
VALUE aproc;
char filename[FNLEN+1];
} t_dbh;
typedef struct s_dbch {
DBC *dbc;
char filename[FNLEN+1];
} t_dbch;
typedef struct s_envh {
DB_ENV *env;
} t_envh;
#define ci(b,m) \
rb_define_const(b,#m,INT2FIX(m))
#define cs(b,m) \
rb_define_const(b,#m,rb_str_new2(m))
#define simple_set(fname) \
VALUE db_ ## fname ## _eq(VALUE obj, VALUE v) \
{ \
rb_ivar_set(obj,fv_ ## fname,v); \
return obj; \
}
#endif

69
extconf.rb Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env ruby
require 'mkmf'
mj,mi,rv=RUBY_VERSION.split('.').collect {|s| s.to_i}
ri=(((mj*1000)+mi)*1000)+rv
if ri < 1008004
$stderr.puts("Version 1.8.4 minimum required")
exit(3)
end
inc_dir,lib_dir = dir_config('bdb2')
$stderr.puts("lib_dir=#{lib_dir} inc_dir=#{inc_dir}")
#case Config::CONFIG["arch"]
#when /solaris2/
# $DLDFLAGS ||= ""
# $DLDFLAGS += " -R#{lib_dir}"
#end
versions=%w(db-4.3 db-4.2)
locations=%w(/usr/local/lib /opt/local/lib /usr/local/BerkeleyDB.4.2/lib /usr/local/BerkeleyDB.4.3/lib)
until versions.empty?
(lib_ok=find_library(this_version=versions.shift,'db_create',*locations)) && break
end
h_locations=%w(/usr/local/include /opt/local/include /usr/local/BerkeleyDB.4.2/include /usr/local/BerkeleyDB.4.3/include)
h_headers=%w(db4/db.h db.h)
until h_headers.empty?
(inc_ok=find_header(this_h=h_headers.shift,*h_locations)) && break
end
# Find db.h, not sure this will work everywhere, gcc is ok
src=create_tmpsrc("#include <#{this_h}>")
cmd=cpp_command("-M")
r=`#{cmd}`
header_loc=r.split.collect {|k| k if k =~ %r{^/.*db.h} }.compact[0]
message("header is #{header_loc}\n")
inc="#include <#{this_h}>"
n=0
message("Writing bdb_aux.c (defines), this takes a while")
defines=[]
File.open(header_loc) {|fd|
File.open("bdb_aux._c","w") {|hd|
hd.puts("/* This file automatically generated by extconf.rb */\n")
fd.each_line {|l|
if l =~ %r{^#define\s+(DBC?_\w*)\s+(\"?)} and macro_defined?($1,inc)
if $2 == '"'
hd.print(%Q{ cs(mBdb,%s);\n}%[$1])
else
hd.print(%Q{ ci(mBdb,%s);\n}%[$1])
end
message(".")
n+=1
end
}
}
}
message("\nwrote #{n} defines\n")
if lib_ok and inc_ok
create_makefile('bdb2')
else
$stderr.puts("cannot create Makefile")
end

15
status.txt Normal file
View file

@ -0,0 +1,15 @@
9-Feb-2006
Done:
All common environment, database and cursor functions are in. They are also
_fully_ unit tested and are in production under heavy use. There are a
few missing, like pget.
Limitations:
If Ruby process exits and there are any open cursors (your program did not
close them) the process will SEGV. This is due to dbc_free trying to close
the cursor(s), even though the DB may already be closed. This will be fixed
by keeping track of open cursors and automatically closing then when
db->close is called, either by calling close or during finalization. Other
than messing up your BDB environment, the SEGV is larglely harmless (but
annoying). It should never happen if your program is written correctly.

157
test.rb Executable file
View file

@ -0,0 +1,157 @@
#!/usr/bin/env ruby
require 'bdb2'
50.times {|n|
db=Bdb::Db.new
db.open("dbtest.db",nil,Bdb::Db::BTREE,Bdb::DB_CREATE,0)
db.put(n.to_s,"ploppy #{n} #{Time.now}",0)
db.close(0)
}
db=Bdb::Db.new
db.open("dbtest.db",nil,nil,nil,0)
500.times {|n|
db.put(n.to_s,"ploppy #{n}",0)
}
db.close(0)
db=Bdb::Db.new
db.open("dbtest.db",nil,nil,nil,0)
dbc=db.cursor(0)
puts("cursor is: "+dbc.inspect.to_s)
kv=dbc.get(nil,nil,Bdb::DB_FIRST);
puts("first data is: " + kv.inspect.to_s)
kv=dbc.get(nil,nil,Bdb::DB_LAST);
puts("last data is: " + kv.inspect.to_s)
kv=dbc.get(nil,nil,Bdb::DB_PREV);
puts("prior data is: " + kv.inspect.to_s)
dbc.del;
begin
kv=dbc.get(nil,nil,Bdb::DB_CURRENT);
rescue Bdb::DbError => m
puts("deleted record is gone from current position:" + m.to_s)
puts("code is #{m.code}")
end
puts("current data is: " +kv.inspect.to_s)
dbc.put("elephant","gorilla",Bdb::DB_KEYFIRST)
kv=dbc.get(nil,nil,Bdb::DB_CURRENT);
puts("current data after put: " +kv.inspect.to_s)
kv=dbc.get(nil,nil,Bdb::DB_LAST);
puts("last data is: " + kv.inspect.to_s)
kv=dbc.get(nil,nil,Bdb::DB_PREV);
puts("prior data is: " + kv.inspect.to_s)
puts("duplicates here is: " + dbc.count.to_s)
dbc.close
db.close(0)
db=Bdb::Db.new
db.open("dbtest.db",nil,nil,nil,0)
5.times {|n|
$stdout.puts(db.get(n.to_s,0))
begin
db.del(n.to_s,0)
rescue
end
}
5.times {|n|
v=db.get(n.to_s,0)
if v
$stdout.puts("For #{n}:" + v)
else
$stdout.puts("-- not in database #{n}")
end
}
db.close(0)
$stderr.puts("All OK!")
File.delete('db1.db') if File.exist?('db1.db')
File.delete('db2.db') if File.exist?('db2.db')
db=Bdb::Db.new
db.open("db1.db",nil,Bdb::Db::HASH,Bdb::DB_CREATE,0)
db2=Bdb::Db.new
db2.flags=Bdb::DB_DUPSORT
db2.open("db2.db",nil,Bdb::Db::HASH,Bdb::DB_CREATE,0)
db.associate(db2,0,
proc {|sdb,key,data|
key.split('-')[0]
})
c=0
File.open("skus") {|fd|
tlen=fd.stat.size
pf=tlen/10
pl=0
fd.each do |line|
c+=1
if c%1000==0
$stderr.print('.')
cp=fd.pos
if ( cp/pf > pl )
pl=cp/pf
$stderr.print(" #{pl*10}% ")
end
end
line.chomp!
n=line*50
isbn,item=line.split('|')[0..1]
sku="%s-%03d"%[isbn,item]
db.put(sku,line,0)
end
}
$stderr.print("\ntotal count: #{c}\n")
db2.close(0)
db.close(0)
$stderr.puts("test environment")
env=Bdb::Env.new
env.cachesize=25*1024*1024;
env.open(".",Bdb::DB_INIT_CDB|Bdb::DB_INIT_MPOOL|Bdb::DB_CREATE,0)
db=env.db
db.open("db1.db",nil,Bdb::Db::HASH,Bdb::DB_CREATE,0)
db2=env.db
db2.flags=Bdb::DB_DUPSORT
db2.open("db2.db",nil,Bdb::Db::HASH,Bdb::DB_CREATE,0)
db.associate(db2,0,
proc {|sdb,key,data|
key.split('-')[0]
})
c=0
File.open("skus") {|fd|
tlen=fd.stat.size
pf=tlen/10
pl=0
fd.each do |line|
c+=1
if c%1000==0
$stderr.print('.')
cp=fd.pos
if ( cp/pf > pl )
pl=cp/pf
$stderr.print(" #{pl*10}% ")
end
end
line.chomp!
n=line*50
isbn,item=line.split('|')[0..1]
sku="%s-%03d"%[isbn,item]
db.put(sku,line,0)
end
}
$stderr.print("\ntotal count: #{c}\n")
db2.close(0)
db.close(0)
env.close
exit
$stderr.puts(Rusage.get.inspect)
$stderr.puts(`ps -up #{$$}`)