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
|
void
|
||||||
rmdbx_val_for( VALUE self, VALUE val, MDBX_val *data )
|
rmdbx_val_for( VALUE self, VALUE val, MDBX_val *data )
|
||||||
{
|
{
|
||||||
VALUE serialize_proc = rb_iv_get( self, "@serializer" );
|
val = rb_funcall( self, rb_intern("serialize"), 1, val );
|
||||||
|
|
||||||
if ( ! NIL_P( serialize_proc ) )
|
|
||||||
val = rb_funcall( serialize_proc, rb_intern("call"), 1, val );
|
|
||||||
|
|
||||||
Check_Type( val, T_STRING );
|
Check_Type( val, T_STRING );
|
||||||
|
|
||||||
data->iov_len = RSTRING_LEN( val );
|
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.
|
* Open the DB environment handle.
|
||||||
*
|
*
|
||||||
|
|
@ -355,7 +337,7 @@ rmdbx_get_val( VALUE self, VALUE key )
|
||||||
switch ( rc ) {
|
switch ( rc ) {
|
||||||
case MDBX_SUCCESS:
|
case MDBX_SUCCESS:
|
||||||
rv = rb_str_new( data.iov_base, data.iov_len );
|
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:
|
case MDBX_NOTFOUND:
|
||||||
return Qnil;
|
return Qnil;
|
||||||
|
|
@ -446,6 +428,10 @@ rmdbx_set_subdb( VALUE self, VALUE name )
|
||||||
size_t len = RSTRING_LEN( name ) + 1;
|
size_t len = RSTRING_LEN( name ) + 1;
|
||||||
db->subdb = malloc( len );
|
db->subdb = malloc( len );
|
||||||
strlcpy( db->subdb, StringValuePtr(name), 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
|
/* 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 ) {
|
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||||
VALUE rv = rb_str_new( data.iov_base, data.iov_len );
|
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 ) {
|
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||||
rv = rb_str_new( data.iov_base, data.iov_len );
|
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 ) {
|
if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
|
||||||
VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
|
VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
|
||||||
VALUE rval = rb_str_new( data.iov_base, data.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 ) {
|
while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
|
||||||
rkey = rb_str_new( key.iov_base, key.iov_len );
|
rkey = rb_str_new( key.iov_base, key.iov_len );
|
||||||
rval = rb_str_new( data.iov_base, data.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
|
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
|
### Yield and return the block, opening a snapshot first if
|
||||||
### there isn't already a transaction in progress. Closes
|
### there isn't already a transaction in progress. Closes
|
||||||
### the snapshot if this method opened it.
|
### 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
|
it "reverts back to previous collection if the block raises an exception" do
|
||||||
expect( db.collection ).to be_nil
|
expect( db.collection ).to be_nil
|
||||||
begin
|
expect {
|
||||||
db.collection( 'bucket1' ) do
|
db.collection( 'bucket1' ) do
|
||||||
db.collection( 'bucket2' ) { raise "ka-bloooey!" }
|
db.collection( 'bucket2' ) { raise "ka-bloooey!" }
|
||||||
end
|
end
|
||||||
rescue
|
}.to raise_error( RuntimeError, /ka-bloooey!/ )
|
||||||
end
|
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
|
expect( db.collection ).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue