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 )
|
db = MDBX::Database.open( 'tmp/testdb', max_collections: 100 )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
23
History.md
23
History.md
|
|
@ -1,6 +1,29 @@
|
||||||
# Release History for MDBX
|
# 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>
|
## v0.2.1 [2021-04-06] Mahlon E. Smith <mahlon@martini.nu>
|
||||||
|
|
||||||
Enhancement:
|
Enhancement:
|
||||||
|
|
|
||||||
1
Rakefile
1
Rakefile
|
|
@ -4,6 +4,7 @@
|
||||||
require 'rake/deveiate'
|
require 'rake/deveiate'
|
||||||
|
|
||||||
Rake::DevEiate.setup( 'mdbx' ) do |project|
|
Rake::DevEiate.setup( 'mdbx' ) do |project|
|
||||||
|
project.publish_to = 'martini.nu:martini/www/docs/ruby-mdbx/'
|
||||||
project.summary = <<~END_SUM
|
project.summary = <<~END_SUM
|
||||||
A ruby binding to libmdbx, an improved version
|
A ruby binding to libmdbx, an improved version
|
||||||
of the Lightning Memory Mapped Database.
|
of the Lightning Memory Mapped Database.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@
|
||||||
*/
|
*/
|
||||||
#define UNWRAP_DB( val, db ) \
|
#define UNWRAP_DB( val, db ) \
|
||||||
rmdbx_db_t *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;
|
VALUE rmdbx_cDatabase;
|
||||||
|
|
@ -160,7 +163,7 @@ rmdbx_open_env( VALUE self )
|
||||||
void
|
void
|
||||||
rmdbx_open_cursor( rmdbx_db_t *db )
|
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." );
|
if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
|
||||||
|
|
||||||
int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
|
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;
|
if ( ! db->txn || db->state.retain_txn > -1 ) return;
|
||||||
|
|
||||||
switch ( txnflag ) {
|
if ( txnflag == RMDBX_TXN_COMMIT ) {
|
||||||
case RMDBX_TXN_COMMIT:
|
|
||||||
mdbx_txn_commit( db->txn );
|
mdbx_txn_commit( db->txn );
|
||||||
default:
|
}
|
||||||
|
else {
|
||||||
mdbx_txn_abort( db->txn );
|
mdbx_txn_abort( db->txn );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,6 +242,7 @@ VALUE
|
||||||
rmdbx_rb_opentxn( VALUE self, VALUE mode )
|
rmdbx_rb_opentxn( VALUE self, VALUE mode )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
|
CHECK_HANDLE;
|
||||||
|
|
||||||
rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
|
rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
|
||||||
db->state.retain_txn = RTEST(mode) ? 1 : 0;
|
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
|
* Empty the current collection on disk. If collections are not enabled
|
||||||
* or the database handle is set to the top-level (main) db - this
|
* 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
|
VALUE
|
||||||
rmdbx_clear( VALUE self )
|
rmdbx_clear( VALUE self )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
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 );
|
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||||
int rc = mdbx_drop( db->txn, db->dbi, true );
|
int rc = mdbx_drop( db->txn, db->dbi, true );
|
||||||
|
|
||||||
|
|
@ -288,10 +331,10 @@ rmdbx_clear( VALUE self )
|
||||||
|
|
||||||
rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
|
rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
|
||||||
|
|
||||||
/* Refresh the environment handles. */
|
/* Reset the current collection to the top level. */
|
||||||
rmdbx_open_env( self );
|
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:
|
/* call-seq:
|
||||||
* db.each_key {|key| block } => self
|
* db.each_key {|key| block } => self
|
||||||
*
|
*
|
||||||
|
|
@ -358,20 +421,41 @@ VALUE
|
||||||
rmdbx_each_key( VALUE self )
|
rmdbx_each_key( VALUE self )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
MDBX_val key, data;
|
int state;
|
||||||
|
|
||||||
|
CHECK_HANDLE;
|
||||||
rmdbx_open_cursor( db );
|
rmdbx_open_cursor( db );
|
||||||
RETURN_ENUMERATOR( self, 0, 0 );
|
RETURN_ENUMERATOR( self, 0, 0 );
|
||||||
|
|
||||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
rb_protect( rmdbx_each_key_i, self, &state );
|
||||||
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 ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mdbx_cursor_close( db->cursor );
|
mdbx_cursor_close( db->cursor );
|
||||||
db->cursor = NULL;
|
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;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,23 +470,43 @@ VALUE
|
||||||
rmdbx_each_value( VALUE self )
|
rmdbx_each_value( VALUE self )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
MDBX_val key, data;
|
int state;
|
||||||
|
|
||||||
|
CHECK_HANDLE;
|
||||||
rmdbx_open_cursor( db );
|
rmdbx_open_cursor( db );
|
||||||
RETURN_ENUMERATOR( self, 0, 0 );
|
RETURN_ENUMERATOR( self, 0, 0 );
|
||||||
|
|
||||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
rb_protect( rmdbx_each_value_i, self, &state );
|
||||||
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 ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mdbx_cursor_close( db->cursor );
|
mdbx_cursor_close( db->cursor );
|
||||||
db->cursor = NULL;
|
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;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,29 +521,24 @@ VALUE
|
||||||
rmdbx_each_pair( VALUE self )
|
rmdbx_each_pair( VALUE self )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
MDBX_val key, data;
|
int state;
|
||||||
|
|
||||||
|
CHECK_HANDLE;
|
||||||
rmdbx_open_cursor( db );
|
rmdbx_open_cursor( db );
|
||||||
RETURN_ENUMERATOR( self, 0, 0 );
|
RETURN_ENUMERATOR( self, 0, 0 );
|
||||||
|
|
||||||
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
rb_protect( rmdbx_each_pair_i, self, &state );
|
||||||
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 ) ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mdbx_cursor_close( db->cursor );
|
mdbx_cursor_close( db->cursor );
|
||||||
db->cursor = NULL;
|
db->cursor = NULL;
|
||||||
|
|
||||||
|
if ( state ) rb_jump_tag( state );
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* call-seq:
|
/* call-seq:
|
||||||
* db.length -> Integer
|
* db.length -> Integer
|
||||||
*
|
*
|
||||||
|
|
@ -451,7 +550,7 @@ rmdbx_length( VALUE self )
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
MDBX_stat mstat;
|
MDBX_stat mstat;
|
||||||
|
|
||||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
CHECK_HANDLE;
|
||||||
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
||||||
|
|
||||||
int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
|
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:
|
/* call-seq:
|
||||||
* db[ 'key' ] => value
|
* db[ 'key' ] => value
|
||||||
*
|
*
|
||||||
|
|
@ -476,7 +608,7 @@ rmdbx_get_val( VALUE self, VALUE key )
|
||||||
int rc;
|
int rc;
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
|
|
||||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
CHECK_HANDLE;
|
||||||
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
|
||||||
|
|
||||||
MDBX_val ckey = rmdbx_key_for( key );
|
MDBX_val ckey = rmdbx_key_for( key );
|
||||||
|
|
@ -503,7 +635,8 @@ rmdbx_get_val( VALUE self, VALUE key )
|
||||||
/* call-seq:
|
/* call-seq:
|
||||||
* db[ 'key' ] = value
|
* 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
|
VALUE
|
||||||
rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||||
|
|
@ -511,7 +644,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||||
int rc;
|
int rc;
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
|
|
||||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
CHECK_HANDLE;
|
||||||
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||||
|
|
||||||
MDBX_val ckey = rmdbx_key_for( key );
|
MDBX_val ckey = rmdbx_key_for( key );
|
||||||
|
|
@ -552,41 +685,32 @@ VALUE
|
||||||
rmdbx_stats( VALUE self )
|
rmdbx_stats( VALUE self )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
UNWRAP_DB( self, db );
|
||||||
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
|
CHECK_HANDLE;
|
||||||
|
|
||||||
return rmdbx_gather_stats( db );
|
return rmdbx_gather_stats( db );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* Return the currently selected collection, or +nil+ if at the
|
||||||
* db.collection -> (collection name, or nil if in main)
|
* top-level.
|
||||||
* db.collection( 'collection_name' ) -> db
|
*/
|
||||||
* db.collection( nil ) -> db (main)
|
VALUE
|
||||||
*
|
rmdbx_get_subdb( VALUE self )
|
||||||
* Gets or sets the sub-database "collection" that read/write
|
{
|
||||||
* operations apply to.
|
UNWRAP_DB( self, db );
|
||||||
* Passing +nil+ sets the database to the main, top-level namespace.
|
return ( db->subdb == NULL ) ? Qnil : rb_str_new_cstr( db->subdb );
|
||||||
* If a block is passed, the collection automatically reverts to the
|
}
|
||||||
* prior collection when it exits.
|
|
||||||
*
|
|
||||||
* db.collection( 'collection_name' ) do
|
/*
|
||||||
* [ ... ]
|
* Sets the current collection name for read/write operations.
|
||||||
* end # reverts to the previous collection name
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
VALUE
|
VALUE
|
||||||
rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
rmdbx_set_subdb( VALUE self, VALUE name )
|
||||||
{
|
{
|
||||||
UNWRAP_DB( self, db );
|
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. */
|
/* Provide a friendlier error message if max_collections is 0. */
|
||||||
if ( db->settings.max_collections == 0 )
|
if ( db->settings.max_collections == 0 )
|
||||||
|
|
@ -596,38 +720,14 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
||||||
if ( db->txn )
|
if ( db->txn )
|
||||||
rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
|
rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
|
||||||
|
|
||||||
/* Retain the prior database collection if a block was passed.
|
db->subdb = NIL_P( name ) ? NULL : StringValueCStr( name );
|
||||||
*/
|
|
||||||
if ( rb_block_given_p() && db->subdb != NULL ) {
|
|
||||||
prev_db = (char *) malloc( strlen(db->subdb) + 1 );
|
|
||||||
strcpy( prev_db, db->subdb );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( NIL_P(subdb) ) {
|
/* Reset the db handle and issue a single transaction to reify
|
||||||
db->subdb = NULL;
|
the collection.
|
||||||
}
|
*/
|
||||||
else {
|
|
||||||
subdb = rb_funcall( subdb, rb_intern("to_s"), 0 );
|
|
||||||
db->subdb = StringValueCStr( subdb );
|
|
||||||
}
|
|
||||||
rmdbx_close_dbi( db );
|
rmdbx_close_dbi( db );
|
||||||
|
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
|
||||||
/*
|
rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
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", rmdbx_database_initialize, -1 );
|
||||||
rb_define_protected_method( rmdbx_cDatabase, "initialize_copy", rmdbx_init_copy, 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, "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_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_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, "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_get_val, 1 );
|
||||||
rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
|
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, "open_transaction", rmdbx_rb_opentxn, 1 );
|
||||||
rb_define_protected_method( rmdbx_cDatabase, "close_transaction", rmdbx_rb_closetxn, 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_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
|
||||||
|
|
||||||
rb_require( "mdbx/database" );
|
rb_require( "mdbx/database" );
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ require 'mdbx_ext'
|
||||||
module MDBX
|
module MDBX
|
||||||
|
|
||||||
# The version of this gem.
|
# The version of this gem.
|
||||||
VERSION = '0.2.1'
|
VERSION = '0.3.0'
|
||||||
|
|
||||||
end # module MDBX
|
end # module MDBX
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,16 +128,43 @@ class MDBX::Database
|
||||||
attr_accessor :deserializer
|
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.
|
### Switch to the top-level collection.
|
||||||
###
|
###
|
||||||
def main
|
def main
|
||||||
return self.collection( nil )
|
return self.set_subdb( nil )
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :namespace, :collection
|
|
||||||
alias_method :size, :length
|
|
||||||
alias_method :each, :each_pair
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Transaction methods
|
# Transaction methods
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ require_relative '../lib/helper'
|
||||||
|
|
||||||
RSpec.describe( MDBX::Database ) do
|
RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
after( :all ) do
|
before( :all ) do
|
||||||
db = described_class.open( TEST_DATABASE.to_s )
|
db = described_class.open( TEST_DATABASE.to_s )
|
||||||
db.clear
|
db.clear
|
||||||
db.close
|
db.close
|
||||||
|
|
@ -52,6 +52,10 @@ RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
let!( :db ) { described_class.open( TEST_DATABASE.to_s ) }
|
let!( :db ) { described_class.open( TEST_DATABASE.to_s ) }
|
||||||
|
|
||||||
|
before( :each ) do
|
||||||
|
db.clear
|
||||||
|
end
|
||||||
|
|
||||||
after( :each ) do
|
after( :each ) do
|
||||||
db.close
|
db.close
|
||||||
end
|
end
|
||||||
|
|
@ -91,6 +95,12 @@ RSpec.describe( MDBX::Database ) do
|
||||||
end
|
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
|
it "can remove an entry by setting a key's value to nil" do
|
||||||
db[ 'test' ] = "hi"
|
db[ 'test' ] = "hi"
|
||||||
expect( db['test'] ).to eq( '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 ) }
|
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_collections: 5 ) }
|
||||||
|
|
||||||
|
before( :each ) do
|
||||||
|
db.clear
|
||||||
|
end
|
||||||
|
|
||||||
after( :each ) do
|
after( :each ) do
|
||||||
db.close
|
db.close
|
||||||
end
|
end
|
||||||
|
|
@ -187,6 +201,11 @@ RSpec.describe( MDBX::Database ) do
|
||||||
} .to raise_exception( /not enabled/ )
|
} .to raise_exception( /not enabled/ )
|
||||||
end
|
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
|
it "knows it's length" do
|
||||||
db.collection( 'size1' )
|
db.collection( 'size1' )
|
||||||
10.times {|i| db[i] = true }
|
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
|
it "disallows regular key/val storage for namespace keys" do
|
||||||
db.collection( 'bucket' )
|
db.collection( 'bucket' )
|
||||||
db[ 'okay' ] = 1
|
db[ 'okay' ] = 1
|
||||||
db.collection( nil )
|
db.main
|
||||||
|
|
||||||
expect{ db['bucket'] = 1 }.to raise_exception( /MDBX_INCOMPATIBLE/ )
|
expect{ db['bucket'] = 1 }.to raise_exception( /MDBX_INCOMPATIBLE/ )
|
||||||
end
|
end
|
||||||
|
|
@ -233,6 +252,37 @@ RSpec.describe( MDBX::Database ) do
|
||||||
expect( db.collection ).to be_nil
|
expect( db.collection ).to be_nil
|
||||||
db.collection( 'bucket' ) { 'no-op' }
|
db.collection( 'bucket' ) { 'no-op' }
|
||||||
expect( db.collection ).to be_nil
|
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
|
end
|
||||||
|
|
||||||
it "can be cleared of contents" do
|
it "can be cleared of contents" do
|
||||||
|
|
@ -242,7 +292,48 @@ RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
db.clear
|
db.clear
|
||||||
db.main
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -251,6 +342,10 @@ RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_collections: 5 ) }
|
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_collections: 5 ) }
|
||||||
|
|
||||||
|
before( :each ) do
|
||||||
|
db.clear
|
||||||
|
end
|
||||||
|
|
||||||
after( :each ) do
|
after( :each ) do
|
||||||
db.close
|
db.close
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue