commit 20a230483cb26875c9d48b7f3eb92155675c8fa3 Author: danj Date: Tue Feb 14 02:56:36 2006 +0000 Initial revision diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..6610c13 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,2 @@ +bdb2.c +rusage.c diff --git a/README b/README new file mode 100644 index 0000000..467054e --- /dev/null +++ b/README @@ -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 + diff --git a/bdb.c b/bdb.c new file mode 100644 index 0000000..2b33e7b --- /dev/null +++ b/bdb.c @@ -0,0 +1,809 @@ +#include +#include + +#define LMEMFLAG 0 + +#ifdef HAVE_STDARG_PROTOTYPES +#include +#define va_init_list(a,b) va_start(a,b) +#else +#include +#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; ilen; 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); +} diff --git a/bdb.h b/bdb.h new file mode 100644 index 0000000..43cd420 --- /dev/null +++ b/bdb.h @@ -0,0 +1,49 @@ + +#ifndef BDB2_H +#define BDB2_H + +#include +#include +#include + + +#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 diff --git a/extconf.rb b/extconf.rb new file mode 100644 index 0000000..31ade46 --- /dev/null +++ b/extconf.rb @@ -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 + diff --git a/status.txt b/status.txt new file mode 100644 index 0000000..1e95490 --- /dev/null +++ b/status.txt @@ -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. + diff --git a/test.rb b/test.rb new file mode 100755 index 0000000..5a25f44 --- /dev/null +++ b/test.rb @@ -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 #{$$}`)