Multiple changes.
- Alter the behavior of #clear, so it doesn't destroy collection environments, but just empties them. - Add #drop, which explictly -does- destroy a collection environment. - Run all cursor methods through rb_protect, to ensure proper cursor cleanup in the event of an exception mid iteration. - Fix the block form of collections to support multiple scopes. - Switching to a collection now automatically creates its environment. - Add include? and has_key?, for presence checks without allocating value memory or requiring deserialization. FossilOrigin-Name: e1ed7bf613981607bb3b57ce7dd3e58b94ea3046e140b6dc37440da8d2909f94
This commit is contained in:
parent
1e04b12efa
commit
ca34f9fdc5
7 changed files with 365 additions and 114 deletions
1
.pryrc
1
.pryrc
|
|
@ -13,4 +13,3 @@ end
|
|||
|
||||
db = MDBX::Database.open( 'tmp/testdb', max_collections: 100 )
|
||||
|
||||
|
||||
|
|
|
|||
23
History.md
23
History.md
|
|
@ -1,6 +1,29 @@
|
|||
# Release History for MDBX
|
||||
|
||||
---
|
||||
## v0.3.0 [2021-04-09] Mahlon E. Smith <mahlon@martini.nu>
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Alter the behavior of #clear, so it doesn't destroy collection
|
||||
environments, but just empties them.
|
||||
|
||||
- Add #drop, which explictly -does- destroy a collection environment.
|
||||
|
||||
- Switching to a collection now automatically creates its environment.
|
||||
|
||||
- Add include? and has_key?, for presence checks without allocating
|
||||
value memory or requiring deserialization.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Run all cursor methods through rb_protect, to ensure proper
|
||||
cursor cleanup in the event of an exception mid iteration.
|
||||
|
||||
- Fix the block form of collections to support multiple scopes.
|
||||
|
||||
|
||||
## v0.2.1 [2021-04-06] Mahlon E. Smith <mahlon@martini.nu>
|
||||
|
||||
Enhancement:
|
||||
|
|
|
|||
1
Rakefile
1
Rakefile
|
|
@ -4,6 +4,7 @@
|
|||
require 'rake/deveiate'
|
||||
|
||||
Rake::DevEiate.setup( 'mdbx' ) do |project|
|
||||
project.publish_to = 'martini.nu:martini/www/docs/ruby-mdbx/'
|
||||
project.summary = <<~END_SUM
|
||||
A ruby binding to libmdbx, an improved version
|
||||
of the Lightning Memory Mapped Database.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@
|
|||
*/
|
||||
#define UNWRAP_DB( val, db ) \
|
||||
rmdbx_db_t *db; \
|
||||
TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db );
|
||||
TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db )
|
||||
|
||||
#define CHECK_HANDLE \
|
||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." )
|
||||
|
||||
|
||||
VALUE rmdbx_cDatabase;
|
||||
|
|
@ -160,7 +163,7 @@ rmdbx_open_env( VALUE self )
|
|||
void
|
||||
rmdbx_open_cursor( rmdbx_db_t *db )
|
||||
{
|
||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||
CHECK_HANDLE;
|
||||
if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
|
||||
|
||||
int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
|
||||
|
|
@ -215,10 +218,10 @@ rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
|
|||
{
|
||||
if ( ! db->txn || db->state.retain_txn > -1 ) return;
|
||||
|
||||
switch ( txnflag ) {
|
||||
case RMDBX_TXN_COMMIT:
|
||||
if ( txnflag == RMDBX_TXN_COMMIT ) {
|
||||
mdbx_txn_commit( db->txn );
|
||||
default:
|
||||
}
|
||||
else {
|
||||
mdbx_txn_abort( db->txn );
|
||||
}
|
||||
|
||||
|
|
@ -239,6 +242,7 @@ VALUE
|
|||
rmdbx_rb_opentxn( VALUE self, VALUE mode )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
CHECK_HANDLE;
|
||||
|
||||
rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
|
||||
db->state.retain_txn = RTEST(mode) ? 1 : 0;
|
||||
|
|
@ -273,13 +277,52 @@ rmdbx_rb_closetxn( VALUE self, VALUE write )
|
|||
*
|
||||
* Empty the current collection on disk. If collections are not enabled
|
||||
* or the database handle is set to the top-level (main) db - this
|
||||
* deletes *all records* from the database. This is not recoverable!
|
||||
* deletes *all records* from the database.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_clear( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
|
||||
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||
int rc = mdbx_drop( db->txn, db->dbi, false );
|
||||
|
||||
if ( rc != MDBX_SUCCESS )
|
||||
rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
|
||||
|
||||
rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* db.drop( collection ) -> db
|
||||
*
|
||||
* Destroy a collection. You must be in the top level database to call
|
||||
* this method.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_drop( VALUE self, VALUE name )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
|
||||
/* Provide a friendlier error message if max_collections is 0. */
|
||||
if ( db->settings.max_collections == 0 )
|
||||
rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: collections are not enabled." );
|
||||
|
||||
/* All transactions must be closed when dropping a database. */
|
||||
if ( db->txn )
|
||||
rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: transaction open" );
|
||||
|
||||
/* A drop can only be performed from the top-level database. */
|
||||
if ( db->subdb != NULL )
|
||||
rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: switch to top-level db first" );
|
||||
|
||||
name = rb_funcall( name, rb_intern("to_s"), 0 );
|
||||
db->subdb = StringValueCStr( name );
|
||||
|
||||
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||
int rc = mdbx_drop( db->txn, db->dbi, true );
|
||||
|
||||
|
|
@ -288,10 +331,10 @@ rmdbx_clear( VALUE self )
|
|||
|
||||
rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
|
||||
|
||||
/* Refresh the environment handles. */
|
||||
rmdbx_open_env( self );
|
||||
/* Reset the current collection to the top level. */
|
||||
db->subdb = NULL;
|
||||
|
||||
return Qnil;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -348,6 +391,26 @@ rmdbx_deserialize( VALUE self, VALUE val )
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Enumerate over keys for the current collection.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_each_key_i( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
MDBX_val key, data;
|
||||
|
||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||
rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
|
||||
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||
rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/* call-seq:
|
||||
* db.each_key {|key| block } => self
|
||||
*
|
||||
|
|
@ -358,20 +421,41 @@ VALUE
|
|||
rmdbx_each_key( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
MDBX_val key, data;
|
||||
int state;
|
||||
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_cursor( db );
|
||||
RETURN_ENUMERATOR( self, 0, 0 );
|
||||
|
||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||
rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
|
||||
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||
rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
|
||||
}
|
||||
}
|
||||
rb_protect( rmdbx_each_key_i, self, &state );
|
||||
|
||||
mdbx_cursor_close( db->cursor );
|
||||
db->cursor = NULL;
|
||||
|
||||
if ( state ) rb_jump_tag( state );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/* Enumerate over values for the current collection.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_each_value_i( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
MDBX_val key, data;
|
||||
|
||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||
VALUE rv = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rmdbx_deserialize( self, rv ) );
|
||||
|
||||
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||
rv = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rmdbx_deserialize( self, rv ) );
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
|
@ -386,23 +470,43 @@ VALUE
|
|||
rmdbx_each_value( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
MDBX_val key, data;
|
||||
int state;
|
||||
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_cursor( db );
|
||||
RETURN_ENUMERATOR( self, 0, 0 );
|
||||
|
||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||
VALUE rv = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rmdbx_deserialize( self, rv ) );
|
||||
|
||||
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||
rv = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rmdbx_deserialize( self, rv ) );
|
||||
}
|
||||
}
|
||||
rb_protect( rmdbx_each_value_i, self, &state );
|
||||
|
||||
mdbx_cursor_close( db->cursor );
|
||||
db->cursor = NULL;
|
||||
|
||||
if ( state ) rb_jump_tag( state );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/* Enumerate over key and value pairs for the current collection.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_each_pair_i( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
MDBX_val key, data;
|
||||
|
||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||
VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
|
||||
VALUE rval = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
|
||||
|
||||
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||
rkey = rb_str_new( key.iov_base, key.iov_len );
|
||||
rval = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
|
@ -417,29 +521,24 @@ VALUE
|
|||
rmdbx_each_pair( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
MDBX_val key, data;
|
||||
int state;
|
||||
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_cursor( db );
|
||||
RETURN_ENUMERATOR( self, 0, 0 );
|
||||
|
||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||
VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
|
||||
VALUE rval = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
|
||||
|
||||
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||
rkey = rb_str_new( key.iov_base, key.iov_len );
|
||||
rval = rb_str_new( data.iov_base, data.iov_len );
|
||||
rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
|
||||
}
|
||||
}
|
||||
rb_protect( rmdbx_each_pair_i, self, &state );
|
||||
|
||||
mdbx_cursor_close( db->cursor );
|
||||
db->cursor = NULL;
|
||||
|
||||
if ( state ) rb_jump_tag( state );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* call-seq:
|
||||
* db.length -> Integer
|
||||
*
|
||||
|
|
@ -451,7 +550,7 @@ rmdbx_length( VALUE self )
|
|||
UNWRAP_DB( self, db );
|
||||
MDBX_stat mstat;
|
||||
|
||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
||||
|
||||
int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
|
||||
|
|
@ -465,6 +564,39 @@ rmdbx_length( VALUE self )
|
|||
}
|
||||
|
||||
|
||||
/* call-seq:
|
||||
* db.include?( 'key' ) => bool
|
||||
*
|
||||
* Returns true if the current collection contains +key+.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_include( VALUE self, VALUE key )
|
||||
{
|
||||
int rc;
|
||||
UNWRAP_DB( self, db );
|
||||
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
||||
|
||||
MDBX_val ckey = rmdbx_key_for( key );
|
||||
MDBX_val data;
|
||||
rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
|
||||
rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
|
||||
|
||||
switch ( rc ) {
|
||||
case MDBX_SUCCESS:
|
||||
return Qtrue;
|
||||
|
||||
case MDBX_NOTFOUND:
|
||||
return Qfalse;
|
||||
|
||||
default:
|
||||
rmdbx_close( self );
|
||||
rb_raise( rmdbx_eDatabaseError, "Unable to fetch key: (%d) %s", rc, mdbx_strerror(rc) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* call-seq:
|
||||
* db[ 'key' ] => value
|
||||
*
|
||||
|
|
@ -476,7 +608,7 @@ rmdbx_get_val( VALUE self, VALUE key )
|
|||
int rc;
|
||||
UNWRAP_DB( self, db );
|
||||
|
||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
||||
|
||||
MDBX_val ckey = rmdbx_key_for( key );
|
||||
|
|
@ -503,7 +635,8 @@ rmdbx_get_val( VALUE self, VALUE key )
|
|||
/* call-seq:
|
||||
* db[ 'key' ] = value
|
||||
*
|
||||
* Set a single value for +key+.
|
||||
* Set a single value for +key+. If the value is +nil+, the
|
||||
* key is removed.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||
|
|
@ -511,7 +644,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
|||
int rc;
|
||||
UNWRAP_DB( self, db );
|
||||
|
||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||
CHECK_HANDLE;
|
||||
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||
|
||||
MDBX_val ckey = rmdbx_key_for( key );
|
||||
|
|
@ -552,41 +685,32 @@ VALUE
|
|||
rmdbx_stats( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
||||
CHECK_HANDLE;
|
||||
|
||||
return rmdbx_gather_stats( db );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* db.collection -> (collection name, or nil if in main)
|
||||
* db.collection( 'collection_name' ) -> db
|
||||
* db.collection( nil ) -> db (main)
|
||||
*
|
||||
* Gets or sets the sub-database "collection" that read/write
|
||||
* operations apply to.
|
||||
* Passing +nil+ sets the database to the main, top-level namespace.
|
||||
* If a block is passed, the collection automatically reverts to the
|
||||
* prior collection when it exits.
|
||||
*
|
||||
* db.collection( 'collection_name' ) do
|
||||
* [ ... ]
|
||||
* end # reverts to the previous collection name
|
||||
* Return the currently selected collection, or +nil+ if at the
|
||||
* top-level.
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_get_subdb( VALUE self )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
return ( db->subdb == NULL ) ? Qnil : rb_str_new_cstr( db->subdb );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Sets the current collection name for read/write operations.
|
||||
*
|
||||
*/
|
||||
VALUE
|
||||
rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
||||
rmdbx_set_subdb( VALUE self, VALUE name )
|
||||
{
|
||||
UNWRAP_DB( self, db );
|
||||
VALUE subdb, block;
|
||||
char *prev_db = NULL;
|
||||
|
||||
rb_scan_args( argc, argv, "01&", &subdb, &block );
|
||||
if ( argc == 0 ) {
|
||||
if ( db->subdb == NULL ) return Qnil;
|
||||
return rb_str_new_cstr( db->subdb );
|
||||
}
|
||||
|
||||
/* Provide a friendlier error message if max_collections is 0. */
|
||||
if ( db->settings.max_collections == 0 )
|
||||
|
|
@ -596,38 +720,14 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
|||
if ( db->txn )
|
||||
rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
|
||||
|
||||
/* Retain the prior database collection if a block was passed.
|
||||
*/
|
||||
if ( rb_block_given_p() && db->subdb != NULL ) {
|
||||
prev_db = (char *) malloc( strlen(db->subdb) + 1 );
|
||||
strcpy( prev_db, db->subdb );
|
||||
}
|
||||
db->subdb = NIL_P( name ) ? NULL : StringValueCStr( name );
|
||||
|
||||
if ( NIL_P(subdb) ) {
|
||||
db->subdb = NULL;
|
||||
}
|
||||
else {
|
||||
subdb = rb_funcall( subdb, rb_intern("to_s"), 0 );
|
||||
db->subdb = StringValueCStr( subdb );
|
||||
}
|
||||
/* Reset the db handle and issue a single transaction to reify
|
||||
the collection.
|
||||
*/
|
||||
rmdbx_close_dbi( db );
|
||||
|
||||
/*
|
||||
FIXME: Immediate transaction write to auto-create new env?
|
||||
Fetching from here at the moment causes an error if you
|
||||
haven't written anything to the new collection yet.
|
||||
*/
|
||||
|
||||
/* Revert to the previous collection after the block is done.
|
||||
*/
|
||||
if ( rb_block_given_p() ) {
|
||||
rb_yield( self );
|
||||
if ( db->subdb != prev_db ) {
|
||||
db->subdb = prev_db;
|
||||
rmdbx_close_dbi( db );
|
||||
}
|
||||
xfree( prev_db );
|
||||
}
|
||||
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||
rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
|
@ -767,16 +867,18 @@ rmdbx_init_database()
|
|||
|
||||
rb_define_protected_method( rmdbx_cDatabase, "initialize", rmdbx_database_initialize, -1 );
|
||||
rb_define_protected_method( rmdbx_cDatabase, "initialize_copy", rmdbx_init_copy, 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, "reopen", rmdbx_open_env, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "closed?", rmdbx_closed_p, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
|
||||
|
||||
rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 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, "drop", rmdbx_drop, 1 );
|
||||
rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "include?", rmdbx_include, 1 );
|
||||
rb_define_method( rmdbx_cDatabase, "length", rmdbx_length, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
|
||||
rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
|
||||
rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
|
||||
|
||||
|
|
@ -784,6 +886,10 @@ rmdbx_init_database()
|
|||
rb_define_protected_method( rmdbx_cDatabase, "open_transaction", rmdbx_rb_opentxn, 1 );
|
||||
rb_define_protected_method( rmdbx_cDatabase, "close_transaction", rmdbx_rb_closetxn, 1 );
|
||||
|
||||
/* Collection functions */
|
||||
rb_define_protected_method( rmdbx_cDatabase, "get_subdb", rmdbx_get_subdb, 0 );
|
||||
rb_define_protected_method( rmdbx_cDatabase, "set_subdb", rmdbx_set_subdb, 1 );
|
||||
|
||||
rb_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
|
||||
|
||||
rb_require( "mdbx/database" );
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ require 'mdbx_ext'
|
|||
module MDBX
|
||||
|
||||
# The version of this gem.
|
||||
VERSION = '0.2.1'
|
||||
VERSION = '0.3.0'
|
||||
|
||||
end # module MDBX
|
||||
|
||||
|
|
|
|||
|
|
@ -128,16 +128,43 @@ class MDBX::Database
|
|||
attr_accessor :deserializer
|
||||
|
||||
|
||||
alias_method :size, :length
|
||||
alias_method :each, :each_pair
|
||||
alias_method :has_key?, :include?
|
||||
|
||||
|
||||
### Gets or sets the sub-database "collection" that read/write
|
||||
### operations apply to. If a block is passed, the collection
|
||||
### automatically reverts to the prior collection when it exits.
|
||||
###
|
||||
### db.collection #=> (collection name, or nil if in main)
|
||||
### db.collection( 'collection_name' ) #=> db
|
||||
###
|
||||
### db.collection( 'collection_name' ) do
|
||||
### [ ... ]
|
||||
### end # reverts to the previous collection name
|
||||
###
|
||||
def collection( name=nil )
|
||||
current = self.get_subdb
|
||||
return current unless name
|
||||
|
||||
self.set_subdb( name.to_s )
|
||||
yield( self ) if block_given?
|
||||
|
||||
return self
|
||||
|
||||
ensure
|
||||
self.set_subdb( current ) if name && block_given?
|
||||
end
|
||||
alias_method :namespace, :collection
|
||||
|
||||
|
||||
### Switch to the top-level collection.
|
||||
###
|
||||
def main
|
||||
return self.collection( nil )
|
||||
return self.set_subdb( nil )
|
||||
end
|
||||
|
||||
alias_method :namespace, :collection
|
||||
alias_method :size, :length
|
||||
alias_method :each, :each_pair
|
||||
|
||||
|
||||
#
|
||||
# Transaction methods
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ require_relative '../lib/helper'
|
|||
|
||||
RSpec.describe( MDBX::Database ) do
|
||||
|
||||
after( :all ) do
|
||||
before( :all ) do
|
||||
db = described_class.open( TEST_DATABASE.to_s )
|
||||
db.clear
|
||||
db.close
|
||||
|
|
@ -52,6 +52,10 @@ RSpec.describe( MDBX::Database ) do
|
|||
|
||||
let!( :db ) { described_class.open( TEST_DATABASE.to_s ) }
|
||||
|
||||
before( :each ) do
|
||||
db.clear
|
||||
end
|
||||
|
||||
after( :each ) do
|
||||
db.close
|
||||
end
|
||||
|
|
@ -91,6 +95,12 @@ RSpec.describe( MDBX::Database ) do
|
|||
end
|
||||
|
||||
|
||||
it "can check for the presence of a key" do
|
||||
expect( db.has_key?( 'key1' ) ).to be_falsey
|
||||
db[ 'key1' ] = 1
|
||||
expect( db.has_key?( 'key1' ) ).to be_truthy
|
||||
end
|
||||
|
||||
it "can remove an entry by setting a key's value to nil" do
|
||||
db[ 'test' ] = "hi"
|
||||
expect( db['test'] ).to eq( 'hi' )
|
||||
|
|
@ -175,6 +185,10 @@ RSpec.describe( MDBX::Database ) do
|
|||
|
||||
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_collections: 5 ) }
|
||||
|
||||
before( :each ) do
|
||||
db.clear
|
||||
end
|
||||
|
||||
after( :each ) do
|
||||
db.close
|
||||
end
|
||||
|
|
@ -187,6 +201,11 @@ RSpec.describe( MDBX::Database ) do
|
|||
} .to raise_exception( /not enabled/ )
|
||||
end
|
||||
|
||||
it "can be used immediately when switched to" do
|
||||
db.collection( :bucket )
|
||||
expect{ db.length }.to_not raise_exception
|
||||
end
|
||||
|
||||
it "knows it's length" do
|
||||
db.collection( 'size1' )
|
||||
10.times {|i| db[i] = true }
|
||||
|
|
@ -202,7 +221,7 @@ RSpec.describe( MDBX::Database ) do
|
|||
it "disallows regular key/val storage for namespace keys" do
|
||||
db.collection( 'bucket' )
|
||||
db[ 'okay' ] = 1
|
||||
db.collection( nil )
|
||||
db.main
|
||||
|
||||
expect{ db['bucket'] = 1 }.to raise_exception( /MDBX_INCOMPATIBLE/ )
|
||||
end
|
||||
|
|
@ -233,6 +252,37 @@ RSpec.describe( MDBX::Database ) do
|
|||
expect( db.collection ).to be_nil
|
||||
db.collection( 'bucket' ) { 'no-op' }
|
||||
expect( db.collection ).to be_nil
|
||||
|
||||
db.collection( 'another' )
|
||||
db.collection( 'bucket' ) { 'no-op' }
|
||||
expect( db.collection ).to eq( 'another' )
|
||||
end
|
||||
|
||||
it "reverts back to previous collections within multiple blocks" do
|
||||
expect( db.collection ).to be_nil
|
||||
db.collection( 'bucket1' ) do
|
||||
expect( db.collection ).to eq( 'bucket1' )
|
||||
db.collection( 'bucket2' ) do
|
||||
expect( db.collection ).to eq( 'bucket2' )
|
||||
db.collection( 'bucket3' ) do
|
||||
expect( db.collection ).to eq( 'bucket3' )
|
||||
end
|
||||
expect( db.collection ).to eq( 'bucket2' )
|
||||
end
|
||||
expect( db.collection ).to eq( 'bucket1' )
|
||||
end
|
||||
expect( db.collection ).to be_nil
|
||||
end
|
||||
|
||||
it "reverts back to previous collection if the block raises an exception" do
|
||||
expect( db.collection ).to be_nil
|
||||
begin
|
||||
db.collection( 'bucket1' ) do
|
||||
db.collection( 'bucket2' ) { raise "ka-bloooey!" }
|
||||
end
|
||||
rescue
|
||||
end
|
||||
expect( db.collection ).to be_nil
|
||||
end
|
||||
|
||||
it "can be cleared of contents" do
|
||||
|
|
@ -242,7 +292,48 @@ RSpec.describe( MDBX::Database ) do
|
|||
|
||||
db.clear
|
||||
db.main
|
||||
expect( db['bucket'] ).to be_nil
|
||||
expect( db ).to include( 'bucket' )
|
||||
end
|
||||
|
||||
it "fail if the max_collections option is not enabled when dropping" do
|
||||
db.close
|
||||
db = described_class.open( TEST_DATABASE.to_s )
|
||||
expect{
|
||||
db.drop( 'bucket' )
|
||||
} .to raise_exception( /not enabled/ )
|
||||
end
|
||||
|
||||
it "disallows dropping a collection mid transaction" do
|
||||
expect {
|
||||
db.transaction { db.drop(:bucket) }
|
||||
}.to raise_exception( MDBX::DatabaseError, /transaction open/ )
|
||||
end
|
||||
|
||||
it "disallows dropping a collection within a collection" do
|
||||
expect {
|
||||
db.collection(:bucket) { db.drop(:bucket) }
|
||||
}.to raise_exception( MDBX::DatabaseError, /switch to top-level/ )
|
||||
end
|
||||
|
||||
it "sets the current collection to 'main' after dropping a collection" do
|
||||
db.collection( 'doom' )
|
||||
db.main
|
||||
db.drop( 'doom' )
|
||||
|
||||
expect( db.collection ).to be_nil
|
||||
expect( db['doom'] ).to be_nil
|
||||
end
|
||||
|
||||
it "retains the collection environment when clearing data" do
|
||||
db.collection( 'doom' )
|
||||
db[ 'key' ] = 1
|
||||
db.clear
|
||||
|
||||
expect( db.collection ).to eq( 'doom' )
|
||||
expect( db ).to_not have_key( 'key' )
|
||||
|
||||
db.main
|
||||
expect( db ).to include( 'doom' )
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -251,6 +342,10 @@ RSpec.describe( MDBX::Database ) do
|
|||
|
||||
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_collections: 5 ) }
|
||||
|
||||
before( :each ) do
|
||||
db.clear
|
||||
end
|
||||
|
||||
after( :each ) do
|
||||
db.close
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue