Checkpoint commit.
Fleshing out collections, automatic serialization of values. Stringify all keys. FossilOrigin-Name: 8bb5e27eacd18bc34b60309a03cdce31921f790c821dab89bf12cd9bfc19825d
This commit is contained in:
parent
9f1388a8de
commit
d92ba7c5eb
3 changed files with 154 additions and 24 deletions
|
|
@ -26,6 +26,7 @@ struct rmdbx_db {
|
||||||
MDBX_cursor *cursor;
|
MDBX_cursor *cursor;
|
||||||
int env_flags;
|
int env_flags;
|
||||||
int open;
|
int open;
|
||||||
|
char *subdb;
|
||||||
};
|
};
|
||||||
typedef struct rmdbx_db rmdbx_db_t;
|
typedef struct rmdbx_db rmdbx_db_t;
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ rmdbx_alloc( VALUE klass )
|
||||||
* removed.
|
* removed.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
rmdbx_destroy( rmdbx_db_t* db )
|
rmdbx_close_all( rmdbx_db_t* db )
|
||||||
{
|
{
|
||||||
if ( db->cursor ) mdbx_cursor_close( db->cursor );
|
if ( db->cursor ) mdbx_cursor_close( db->cursor );
|
||||||
if ( db->txn ) mdbx_txn_abort( db->txn );
|
if ( db->txn ) mdbx_txn_abort( db->txn );
|
||||||
|
|
@ -74,20 +75,20 @@ void
|
||||||
rmdbx_free( void *db )
|
rmdbx_free( void *db )
|
||||||
{
|
{
|
||||||
if ( db ) {
|
if ( db ) {
|
||||||
rmdbx_destroy( db );
|
rmdbx_close_all( db );
|
||||||
free( db );
|
free( db );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cleanly close an opened database.
|
* Cleanly close an opened database from Ruby.
|
||||||
*/
|
*/
|
||||||
VALUE
|
VALUE
|
||||||
rmdbx_close( VALUE self )
|
rmdbx_close( VALUE self )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
rmdbx_destroy( db );
|
rmdbx_close_all( db );
|
||||||
return Qtrue;
|
return Qtrue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,8 +97,7 @@ rmdbx_close( VALUE self )
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* db.closed? #=> false
|
* db.closed? #=> false
|
||||||
*
|
*
|
||||||
* Predicate: return true if the database has been closed,
|
* Predicate: return true if the database handle is closed.
|
||||||
* (or never was actually opened for some reason?)
|
|
||||||
*/
|
*/
|
||||||
VALUE
|
VALUE
|
||||||
rmdbx_closed_p( VALUE self )
|
rmdbx_closed_p( VALUE self )
|
||||||
|
|
@ -126,7 +126,7 @@ rmdbx_open_txn( VALUE self, int rwflag )
|
||||||
|
|
||||||
if ( db->dbi == 0 ) {
|
if ( db->dbi == 0 ) {
|
||||||
// FIXME: dbi_flags
|
// FIXME: dbi_flags
|
||||||
rc = mdbx_dbi_open( db->txn, NULL, 0, &db->dbi );
|
rc = mdbx_dbi_open( db->txn, db->subdb, MDBX_CREATE, &db->dbi );
|
||||||
if ( rc != MDBX_SUCCESS ) {
|
if ( rc != MDBX_SUCCESS ) {
|
||||||
rmdbx_close( self );
|
rmdbx_close( self );
|
||||||
rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
|
rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
|
||||||
|
|
@ -137,16 +137,61 @@ rmdbx_open_txn( VALUE self, int rwflag )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* db.destroy
|
||||||
|
*
|
||||||
|
* Empty the database (or subdatabase) on disk. Unrecoverable.
|
||||||
|
*/
|
||||||
|
VALUE
|
||||||
|
rmdbx_destroy( VALUE self )
|
||||||
|
{
|
||||||
|
UNWRAP_DB( self, db );
|
||||||
|
rmdbx_open_txn( self, MDBX_TXN_READWRITE );
|
||||||
|
int rc = mdbx_drop( db->txn, db->dbi, true );
|
||||||
|
|
||||||
|
// FIXME: something fishy here
|
||||||
|
//
|
||||||
|
if ( rc != 0 )
|
||||||
|
rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
|
||||||
|
mdbx_txn_commit( db->txn );
|
||||||
|
db->open = 0;
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given a ruby +arg+, convert and return a structure
|
* Given a ruby +arg+, convert and return a structure
|
||||||
* suitable for usage as a key for mdbx.
|
* suitable for usage as a key for mdbx. All keys are explicitly
|
||||||
|
* converted to strings.
|
||||||
*/
|
*/
|
||||||
MDBX_val
|
MDBX_val
|
||||||
rmdbx_vec_for( VALUE arg )
|
rmdbx_key_for( VALUE arg )
|
||||||
{
|
{
|
||||||
MDBX_val rv;
|
MDBX_val rv;
|
||||||
|
|
||||||
// FIXME: arbitrary data types!
|
arg = rb_funcall( arg, rb_intern("to_s"), 0 );
|
||||||
|
rv.iov_len = RSTRING_LEN( arg );
|
||||||
|
rv.iov_base = StringValuePtr( arg );
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a ruby +arg+, convert and return a structure
|
||||||
|
* suitable for usage as a value for mdbx.
|
||||||
|
*/
|
||||||
|
MDBX_val
|
||||||
|
rmdbx_val_for( VALUE self, VALUE arg )
|
||||||
|
{
|
||||||
|
MDBX_val rv;
|
||||||
|
VALUE serialize_proc;
|
||||||
|
|
||||||
|
serialize_proc = rb_iv_get( self, "@serializer" );
|
||||||
|
if ( ! NIL_P( serialize_proc ) )
|
||||||
|
arg = rb_funcall( serialize_proc, rb_intern("call"), 1, arg );
|
||||||
|
|
||||||
rv.iov_len = RSTRING_LEN( arg );
|
rv.iov_len = RSTRING_LEN( arg );
|
||||||
rv.iov_base = StringValuePtr( arg );
|
rv.iov_base = StringValuePtr( arg );
|
||||||
|
|
||||||
|
|
@ -163,21 +208,27 @@ VALUE
|
||||||
rmdbx_get_val( VALUE self, VALUE key )
|
rmdbx_get_val( VALUE self, VALUE key )
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
VALUE deserialize_proc;
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
|
|
||||||
if ( RTEST(rmdbx_closed_p(self)) ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
if ( RTEST(rmdbx_closed_p(self)) ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||||
|
|
||||||
rmdbx_open_txn( self, MDBX_TXN_RDONLY );
|
rmdbx_open_txn( self, MDBX_TXN_RDONLY );
|
||||||
|
|
||||||
MDBX_val ckey = rmdbx_vec_for( key );
|
MDBX_val ckey = rmdbx_key_for( key );
|
||||||
MDBX_val data;
|
MDBX_val data;
|
||||||
rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
|
rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
|
||||||
mdbx_txn_abort( db->txn );
|
mdbx_txn_abort( db->txn );
|
||||||
|
|
||||||
switch ( rc ) {
|
switch ( rc ) {
|
||||||
case MDBX_SUCCESS:
|
case MDBX_SUCCESS:
|
||||||
// FIXME: arbitrary data types!
|
deserialize_proc = rb_iv_get( self, "@deserializer" );
|
||||||
|
if ( ! NIL_P( deserialize_proc ) ) {
|
||||||
|
return rb_funcall( deserialize_proc, rb_intern("call"), 1, rb_str_new2(data.iov_base) );
|
||||||
|
}
|
||||||
|
else {
|
||||||
return rb_str_new2( data.iov_base );
|
return rb_str_new2( data.iov_base );
|
||||||
|
}
|
||||||
|
|
||||||
case MDBX_NOTFOUND:
|
case MDBX_NOTFOUND:
|
||||||
return Qnil;
|
return Qnil;
|
||||||
|
|
@ -203,7 +254,8 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||||
if ( RTEST(rmdbx_closed_p(self)) ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
if ( RTEST(rmdbx_closed_p(self)) ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||||
|
|
||||||
rmdbx_open_txn( self, MDBX_TXN_READWRITE );
|
rmdbx_open_txn( self, MDBX_TXN_READWRITE );
|
||||||
MDBX_val ckey = rmdbx_vec_for( key );
|
|
||||||
|
MDBX_val ckey = rmdbx_key_for( key );
|
||||||
|
|
||||||
// FIXME: DUPSORT is enabled -- different api?
|
// FIXME: DUPSORT is enabled -- different api?
|
||||||
// See: MDBX_NODUPDATA / MDBX_NOOVERWRITE
|
// See: MDBX_NODUPDATA / MDBX_NOOVERWRITE
|
||||||
|
|
@ -212,7 +264,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
MDBX_val old;
|
MDBX_val old;
|
||||||
MDBX_val data = rmdbx_vec_for( val );
|
MDBX_val data = rmdbx_val_for( self, val );
|
||||||
rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
|
rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,6 +279,45 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* db.collection( 'collection_name' ) # => db
|
||||||
|
* db.collection( nil ) # => db (main)
|
||||||
|
*
|
||||||
|
* Operate on a sub-database "collection". Passing +nil+
|
||||||
|
* sets the database to the main, top-level namespace.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
VALUE
|
||||||
|
rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
||||||
|
/* rmdbx_set_subdb( VALUE self, VALUE subdb ) */
|
||||||
|
{
|
||||||
|
UNWRAP_DB( self, db );
|
||||||
|
VALUE subdb;
|
||||||
|
|
||||||
|
rb_scan_args( argc, argv, "01", &subdb );
|
||||||
|
if ( argc == 0 ) {
|
||||||
|
if ( db->subdb == NULL ) return Qnil;
|
||||||
|
return rb_str_new2( db->subdb );
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_iv_set( self, "@collection", subdb );
|
||||||
|
db->subdb = NIL_P( subdb ) ? NULL : StringValueCStr( subdb );
|
||||||
|
|
||||||
|
// Close any current dbi handle, to be re-opened with
|
||||||
|
// the new collection on next access.
|
||||||
|
//
|
||||||
|
// FIXME: Immediate transaction write to auto-create new env
|
||||||
|
//
|
||||||
|
if ( db->dbi ) {
|
||||||
|
mdbx_dbi_close( db->env, db->dbi );
|
||||||
|
db->dbi = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* MDBX::Database.open( path ) -> db
|
* MDBX::Database.open( path ) -> db
|
||||||
|
|
@ -299,6 +390,7 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
|
||||||
db->cursor = NULL;
|
db->cursor = NULL;
|
||||||
db->env_flags = env_flags;
|
db->env_flags = env_flags;
|
||||||
db->open = 0;
|
db->open = 0;
|
||||||
|
db->subdb = NULL;
|
||||||
|
|
||||||
/* Allocate an mdbx environment.
|
/* Allocate an mdbx environment.
|
||||||
*/
|
*/
|
||||||
|
|
@ -306,6 +398,9 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
|
||||||
if ( rc != MDBX_SUCCESS )
|
if ( rc != MDBX_SUCCESS )
|
||||||
rb_raise( rmdbx_eDatabaseError, "mdbx_env_create: (%d) %s", rc, mdbx_strerror(rc) );
|
rb_raise( rmdbx_eDatabaseError, "mdbx_env_create: (%d) %s", rc, mdbx_strerror(rc) );
|
||||||
|
|
||||||
|
//FIXME: configurable mdbx_env_set_maxdbs( db->env, 20 );
|
||||||
|
mdbx_env_set_maxdbs( db->env, 20 );
|
||||||
|
|
||||||
/* Open the DB handle on disk.
|
/* Open the DB handle on disk.
|
||||||
*/
|
*/
|
||||||
rc = mdbx_env_open( db->env, StringValueCStr(path), env_flags, mode );
|
rc = mdbx_env_open( db->env, StringValueCStr(path), env_flags, mode );
|
||||||
|
|
@ -335,8 +430,10 @@ rmdbx_init_database()
|
||||||
rb_define_alloc_func( rmdbx_cDatabase, rmdbx_alloc );
|
rb_define_alloc_func( rmdbx_cDatabase, rmdbx_alloc );
|
||||||
|
|
||||||
rb_define_protected_method( rmdbx_cDatabase, "initialize", rmdbx_database_initialize, -1 );
|
rb_define_protected_method( rmdbx_cDatabase, "initialize", rmdbx_database_initialize, -1 );
|
||||||
|
rb_define_method( rmdbx_cDatabase, "collection", rmdbx_set_subdb, -1 );
|
||||||
rb_define_method( rmdbx_cDatabase, "close", rmdbx_close, 0 );
|
rb_define_method( rmdbx_cDatabase, "close", rmdbx_close, 0 );
|
||||||
rb_define_method( rmdbx_cDatabase, "closed?", rmdbx_closed_p, 0 );
|
rb_define_method( rmdbx_cDatabase, "closed?", rmdbx_closed_p, 0 );
|
||||||
|
rb_define_method( rmdbx_cDatabase, "destroy", rmdbx_destroy, 0 );
|
||||||
rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
|
rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
|
||||||
rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
|
rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,11 @@ class MDBX::Database
|
||||||
###
|
###
|
||||||
def self::open( *args, &block )
|
def self::open( *args, &block )
|
||||||
db = new( *args )
|
db = new( *args )
|
||||||
return db unless block_given?
|
|
||||||
|
|
||||||
|
db.serializer = ->( v ) { Marshal.dump( v ) }
|
||||||
|
db.deserializer = ->( v ) { Marshal.load( v ) }
|
||||||
|
|
||||||
|
if block_given?
|
||||||
begin
|
begin
|
||||||
yield db
|
yield db
|
||||||
ensure
|
ensure
|
||||||
|
|
@ -30,6 +33,10 @@ class MDBX::Database
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return db
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Only instantiate Database objects via #open.
|
# Only instantiate Database objects via #open.
|
||||||
private_class_method :new
|
private_class_method :new
|
||||||
|
|
||||||
|
|
@ -39,5 +46,11 @@ class MDBX::Database
|
||||||
# The path on disk of the database.
|
# The path on disk of the database.
|
||||||
attr_reader :path
|
attr_reader :path
|
||||||
|
|
||||||
|
# A Proc for automatically serializing values.
|
||||||
|
attr_accessor :serializer
|
||||||
|
|
||||||
|
# A Proc for automatically deserializing values.
|
||||||
|
attr_accessor :deserializer
|
||||||
|
|
||||||
end # class MDBX::Database
|
end # class MDBX::Database
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,24 @@ RSpec.describe( MDBX::Database ) do
|
||||||
expect( db.closed? ).to be_truthy
|
expect( db.closed? ).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'an opened database' do
|
it "closes itself automatically when used in block form" do
|
||||||
|
db = described_class.open( TEST_DATABASE.to_s ) do |db|
|
||||||
before( :each ) do
|
expect( db.closed? ).to be_falsey
|
||||||
@db = described_class.open( TEST_DATABASE.to_s )
|
end
|
||||||
|
expect( db.closed? ).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
context 'an opened database' do
|
||||||
|
|
||||||
|
let!( :db ) { described_class.open( TEST_DATABASE.to_s ) }
|
||||||
|
|
||||||
after( :each ) do
|
after( :each ) do
|
||||||
@db.close
|
db.close
|
||||||
|
end
|
||||||
|
|
||||||
|
it "knows its own path" do
|
||||||
|
expect( db.path ).to match( %r|data/testdb$| )
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fails if opened again within the same process" do
|
it "fails if opened again within the same process" do
|
||||||
|
|
@ -36,6 +46,16 @@ RSpec.describe( MDBX::Database ) do
|
||||||
to raise_exception( MDBX::DatabaseError, /environment is already used/ )
|
to raise_exception( MDBX::DatabaseError, /environment is already used/ )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "defaults to the top-level namespace" do
|
||||||
|
expect( db.collection ).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can set a collection namespace" do
|
||||||
|
db.collection( 'bucket' )
|
||||||
|
expect( db.collection ).to eq( 'bucket' )
|
||||||
|
# TODO: set/retrieve data
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue