Multiple changes.

- Complete first round of documentation.
  - Complete first round of tests and coverage.
  - Expand the thread benchmarker for testing metasync.
  - Add enumerators (each_key/each_value/each_pair) using cursors.
  - Remove keys() implementation in favor of using the emumerable.
  - Make deserialization more DRY.
  - Add an efficient length() method.
  - Add various Hash-alike methods.
  - General code cleanup for release.

FossilOrigin-Name: 0d2bd3995f203c9ac1734ac3da32dd2f09efda9a394e554e6006e44dd07a33b0
This commit is contained in:
Mahlon E. Smith 2021-03-14 23:19:41 +00:00
parent 907cdecfc5
commit c0cd35e260
8 changed files with 828 additions and 271 deletions

View file

@ -75,7 +75,7 @@ rmdbx_free( void *db )
/*
* Cleanly close an opened database from Ruby.
* Cleanly close an opened database.
*/
VALUE
rmdbx_close( VALUE self )
@ -154,6 +154,25 @@ rmdbx_open_env( VALUE self )
}
/*
* Open a cursor for iteration.
*/
void
rmdbx_open_cursor( rmdbx_db_t *db )
{
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
if ( rc != MDBX_SUCCESS ) {
rmdbx_close_all( db );
rb_raise( rmdbx_eDatabaseError, "Unable to open cursor: (%d) %s", rc, mdbx_strerror(rc) );
}
return;
}
/*
* Open a new database transaction. If a transaction is already
* open, this is a no-op.
@ -165,7 +184,7 @@ rmdbx_open_txn( rmdbx_db_t *db, int rwflag )
{
if ( db->txn ) return;
int rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn);
int rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn );
if ( rc != MDBX_SUCCESS ) {
rmdbx_close_all( db );
rb_raise( rmdbx_eDatabaseError, "mdbx_txn_begin: (%d) %s", rc, mdbx_strerror(rc) );
@ -254,7 +273,7 @@ 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 data* on disk. Fair warning, this is not recoverable!
* deletes *all records* from the database. This is not recoverable!
*/
VALUE
rmdbx_clear( VALUE self )
@ -315,40 +334,133 @@ rmdbx_val_for( VALUE self, VALUE arg )
}
/* call-seq:
* db.keys => [ 'key1', 'key2', ... ]
*
* Return an array of all keys in the current collection.
/*
* Deserialize and return a value.
*/
VALUE
rmdbx_keys( VALUE self )
rmdbx_deserialize( VALUE self, VALUE val )
{
VALUE deserialize_proc = rb_iv_get( self, "@deserializer" );
if ( ! NIL_P( deserialize_proc ) )
val = rb_funcall( deserialize_proc, rb_intern("call"), 1, val );
return val;
}
/* call-seq:
* db.each_key {|key| block } => self
*
* Calls the block once for each key, returning self.
* A transaction must be opened prior to use.
*/
VALUE
rmdbx_each_key( VALUE self )
{
UNWRAP_DB( self, db );
VALUE rv = rb_ary_new();
MDBX_val key, data;
int rc;
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_cursor( db );
RETURN_ENUMERATOR( self, 0, 0 );
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor);
if ( rc != MDBX_SUCCESS ) {
rmdbx_close( self );
rb_raise( rmdbx_eDatabaseError, "Unable to open cursor: (%d) %s", rc, mdbx_strerror(rc) );
}
rc = mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST );
if ( rc == MDBX_SUCCESS ) {
rb_ary_push( rv, rb_str_new( key.iov_base, key.iov_len ) );
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == 0 ) {
rb_ary_push( rv, rb_str_new( key.iov_base, key.iov_len ) );
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 ) );
}
}
mdbx_cursor_close( db->cursor );
db->cursor = NULL;
return self;
}
/* call-seq:
* db.each_value {|value| block } => self
*
* Calls the block once for each value, returning self.
* A transaction must be opened prior to use.
*/
VALUE
rmdbx_each_value( VALUE self )
{
UNWRAP_DB( self, db );
MDBX_val key, data;
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 ) );
}
}
mdbx_cursor_close( db->cursor );
db->cursor = NULL;
return self;
}
/* call-seq:
* db.each_pair {|key, value| block } => self
*
* Calls the block once for each key and value, returning self.
* A transaction must be opened prior to use.
*/
VALUE
rmdbx_each_pair( VALUE self )
{
UNWRAP_DB( self, db );
MDBX_val key, data;
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 ) ) );
}
}
mdbx_cursor_close( db->cursor );
db->cursor = NULL;
return self;
}
/* call-seq:
* db.length -> Integer
*
* Returns the count of keys in the currently selected collection.
*/
VALUE
rmdbx_length( VALUE self )
{
UNWRAP_DB( self, db );
MDBX_stat mstat;
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
if ( rc != MDBX_SUCCESS )
rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_stat: (%d) %s", rc, mdbx_strerror(rc) );
VALUE rv = LONG2FIX( mstat.ms_entries );
rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
return rv;
}
@ -356,31 +468,27 @@ rmdbx_keys( VALUE self )
/* call-seq:
* db[ 'key' ] => value
*
* Convenience method: return a single value for +key+ immediately.
* Return a single value for +key+ immediately.
*/
VALUE
rmdbx_get_val( VALUE self, VALUE key )
{
int rc;
VALUE deserialize_proc;
UNWRAP_DB( self, db );
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
MDBX_val ckey = rmdbx_key_for( key );
MDBX_val data;
VALUE rv;
rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
switch ( rc ) {
case MDBX_SUCCESS:
deserialize_proc = rb_iv_get( self, "@deserializer" );
VALUE rv = rb_str_new( data.iov_base, data.iov_len );
if ( ! NIL_P( deserialize_proc ) )
return rb_funcall( deserialize_proc, rb_intern("call"), 1, rv );
return rv;
rv = rb_str_new( data.iov_base, data.iov_len );
return rmdbx_deserialize( self, rv );
case MDBX_NOTFOUND:
return Qnil;
@ -395,7 +503,7 @@ rmdbx_get_val( VALUE self, VALUE key )
/* call-seq:
* db[ 'key' ] = value
*
* Convenience method: set a single value for +key+
* Set a single value for +key+.
*/
VALUE
rmdbx_put_val( VALUE self, VALUE key, VALUE val )
@ -404,7 +512,6 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
UNWRAP_DB( self, db );
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_txn( db, MDBX_TXN_READWRITE );
MDBX_val ckey = rmdbx_key_for( key );
@ -453,8 +560,9 @@ rmdbx_stats( VALUE self )
/*
* call-seq:
* db.collection( 'collection_name' ) => db
* db.collection( nil ) => db (main)
* 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.
@ -464,7 +572,7 @@ rmdbx_stats( VALUE self )
*
* db.collection( 'collection_name' ) do
* [ ... ]
* end => reverts to the previous collection name
* end # reverts to the previous collection name
*
*/
VALUE
@ -482,22 +590,19 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
/* Provide a friendlier error message if max_collections is 0. */
if ( db->settings.max_collections == 0 )
rb_raise( rmdbx_eDatabaseError, "Unable to change collection: collections are not enabled." );
rb_raise( rmdbx_eDatabaseError, "Unable to change collection: collections are not enabled." );
/* All transactions must be closed when switching database handles. */
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() ) {
if ( db->subdb != NULL ) {
prev_db = (char *) malloc( strlen(db->subdb) + 1 );
strcpy( prev_db, db->subdb );
}
/* 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 );
}
rb_iv_set( self, "@collection", subdb );
db->subdb = NIL_P( subdb ) ? NULL : StringValueCStr( subdb );
rmdbx_close_dbi( db );
@ -507,11 +612,11 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
haven't written anything to the new collection yet.
*/
/* Revert to the previous collection after the block is done. */
/* Revert to the previous collection after the block is done.
*/
if ( rb_block_given_p() ) {
rb_yield( self );
if ( db->subdb != prev_db ) {
rb_iv_set( self, "@collection", prev_db ? rb_str_new_cstr(prev_db) : Qnil );
db->subdb = prev_db;
rmdbx_close_dbi( db );
}
@ -633,7 +738,10 @@ rmdbx_init_database()
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, "keys", rmdbx_keys, 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, "length", rmdbx_length, 0 );
rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );