Move de/serialization to ruby for easier error detection.
Closes open transactions if de/serialization fails for any reason, avoiding the object to be in a potentially confused state. Thanks to Michael Granger (ged@faeriemud.org) for the heads up and test case. FossilOrigin-Name: e6e52675510533da8a26b0e2f0b2f73505a3b4ee0c94b123c37089489ed7745a
This commit is contained in:
parent
6cc96d8fae
commit
25655d0554
4 changed files with 56 additions and 29 deletions
|
|
@ -1 +1 @@
|
|||
2.7.2
|
||||
2.7
|
||||
|
|
|
|||
|
|
@ -135,11 +135,7 @@ rmdbx_key_for( VALUE key, MDBX_val *ckey )
|
|||
void
|
||||
rmdbx_val_for( VALUE self, VALUE val, MDBX_val *data )
|
||||
{
|
||||
VALUE serialize_proc = rb_iv_get( self, "@serializer" );
|
||||
|
||||
if ( ! NIL_P( serialize_proc ) )
|
||||
val = rb_funcall( serialize_proc, rb_intern("call"), 1, val );
|
||||
|
||||
val = rb_funcall( self, rb_intern("serialize"), 1, val );
|
||||
Check_Type( val, T_STRING );
|
||||
|
||||
data->iov_len = RSTRING_LEN( val );
|
||||
|
|
@ -148,20 +144,6 @@ rmdbx_val_for( VALUE self, VALUE val, MDBX_val *data )
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Deserialize and return a value.
|
||||
*/
|
||||
VALUE
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Open the DB environment handle.
|
||||
*
|
||||
|
|
@ -355,7 +337,7 @@ rmdbx_get_val( VALUE self, VALUE key )
|
|||
switch ( rc ) {
|
||||
case MDBX_SUCCESS:
|
||||
rv = rb_str_new( data.iov_base, data.iov_len );
|
||||
return rmdbx_deserialize( self, rv );
|
||||
return rb_funcall( self, rb_intern("deserialize"), 1, rv );
|
||||
|
||||
case MDBX_NOTFOUND:
|
||||
return Qnil;
|
||||
|
|
@ -446,6 +428,10 @@ rmdbx_set_subdb( VALUE self, VALUE name )
|
|||
size_t len = RSTRING_LEN( name ) + 1;
|
||||
db->subdb = malloc( len );
|
||||
strlcpy( db->subdb, StringValuePtr(name), len );
|
||||
rmdbx_log_obj( self, "debug", "setting subdb: %s", RSTRING_PTR(name) );
|
||||
}
|
||||
else {
|
||||
rmdbx_log_obj( self, "debug", "clearing subdb" );
|
||||
}
|
||||
|
||||
/* Reset the db handle and issue a single transaction to reify
|
||||
|
|
@ -644,11 +630,11 @@ rmdbx_each_value_i( VALUE self )
|
|||
|
||||
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 ) );
|
||||
rb_yield( rb_funcall( self, rb_intern("deserialize"), 1, 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_yield( rb_funcall( self, rb_intern("deserialize"), 1, rv ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -694,12 +680,15 @@ rmdbx_each_pair_i( VALUE self )
|
|||
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 ) ) );
|
||||
rval = rb_funcall( self, rb_intern("deserialize"), 1, rval );
|
||||
rb_yield( rb_assoc_new( rkey, 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 ) ) );
|
||||
rval = rb_funcall( self, rb_intern("deserialize"), 1, rval );
|
||||
|
||||
rb_yield( rb_assoc_new( rkey, rval ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -364,6 +364,34 @@ class MDBX::Database
|
|||
protected
|
||||
#########
|
||||
|
||||
|
||||
### Safely serialize a value, closing any open transaction and re-raising
|
||||
### if necessary.
|
||||
###
|
||||
def serialize( val )
|
||||
return val unless self.serializer
|
||||
return self.serializer.call( val )
|
||||
|
||||
rescue => err
|
||||
self.close_transaction( false )
|
||||
raise err
|
||||
end
|
||||
|
||||
|
||||
### Safely deserialize a value, closing any open transaction and re-raising
|
||||
### if necessary.
|
||||
###
|
||||
def deserialize( val )
|
||||
return val unless self.deserializer
|
||||
return self.deserializer.call( val )
|
||||
|
||||
rescue => err
|
||||
self.close_transaction( false )
|
||||
raise err
|
||||
end
|
||||
|
||||
|
||||
|
||||
### Yield and return the block, opening a snapshot first if
|
||||
### there isn't already a transaction in progress. Closes
|
||||
### the snapshot if this method opened it.
|
||||
|
|
|
|||
|
|
@ -265,12 +265,22 @@ RSpec.describe( MDBX::Database ) do
|
|||
|
||||
it "reverts back to previous collection if the block raises an exception" do
|
||||
expect( db.collection ).to be_nil
|
||||
begin
|
||||
expect {
|
||||
db.collection( 'bucket1' ) do
|
||||
db.collection( 'bucket2' ) { raise "ka-bloooey!" }
|
||||
end
|
||||
rescue
|
||||
}.to raise_error( RuntimeError, /ka-bloooey!/ )
|
||||
expect( db.collection ).to be_nil
|
||||
end
|
||||
|
||||
it "reverts back to previous collection if serialization fails" do
|
||||
db.serializer = ->( v ) { raise "ka-bloooey!" }
|
||||
expect( db.collection ).to be_nil
|
||||
expect {
|
||||
db.collection( 'bucket1' ) do
|
||||
db.collection( 'bucket2' ) {|sdb| sdb['foo'] = 1 }
|
||||
end
|
||||
}.to raise_error( RuntimeError, /ka-bloooey!/ )
|
||||
expect( db.collection ).to be_nil
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue