Start blocking out some documentation.
- Fix some C rdoc so it is parsed correctly. - Fill out transaction testing. - Populate docs for DB options. FossilOrigin-Name: f54dbfacf2dda100a116fdcc856ca5231e249f23238ca9d4355618e3a380a8f8
This commit is contained in:
parent
81ee69295c
commit
a54c286a75
11 changed files with 345 additions and 43 deletions
3
.pryrc
3
.pryrc
|
|
@ -11,5 +11,6 @@ rescue Exception => e
|
||||||
e.backtrace.join( "\n\t" )
|
e.backtrace.join( "\n\t" )
|
||||||
end
|
end
|
||||||
|
|
||||||
db = MDBX::Database.open( 'tmp/testdb', max_collections: 5 )
|
# db = MDBX::Database.open( 'tmp/testdb' )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
118
README.md
118
README.md
|
|
@ -1,4 +1,5 @@
|
||||||
# Ruby MDBX
|
|
||||||
|
# Ruby::MDBX
|
||||||
|
|
||||||
home
|
home
|
||||||
: https://code.martini.nu/ruby-mdbx
|
: https://code.martini.nu/ruby-mdbx
|
||||||
|
|
@ -99,7 +100,7 @@ development.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2020, Mahlon E. Smith
|
Copyright (c) 2020-2021 Mahlon E. Smith
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -127,3 +128,116 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Ruby MDBX
|
||||||
|
=========
|
||||||
|
|
||||||
|
https://erthink.github.io/libmdbx/intro.html
|
||||||
|
|
||||||
|
Notes on the libmdbx environment for ruby:
|
||||||
|
|
||||||
|
- A **database** is contained in a file, normally wrapped in directory for it's associated lock.
|
||||||
|
- Each database can contain multiple named **collections**.
|
||||||
|
- Each collection can contain any number of **keys**, and their associated **values**. A collection may optionally support multiple values per key (or duplicate keys, which is the same thing).
|
||||||
|
- A **cursor** lets you iterate a collection's keys and values in order.
|
||||||
|
(Note, this should be enumerable and built in to the Ruby interface)
|
||||||
|
- A **snapshot** is a self-consistent read-only view of the database. It stays the same even if some other thread or process makes changes. *The only way to access keys and values is within a snapshot*.
|
||||||
|
- A **transaction** is a writable snapshot. Changes made within a transaction are private until committed. *The only way to modify the database is within a transaction*.
|
||||||
|
|
||||||
|
|
||||||
|
Example Usage
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Create database handle
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
db = MDBX::Database.create( "/path/to/file", options )
|
||||||
|
db = MDBX::Database.open( "/path/to/file", options )
|
||||||
|
|
||||||
|
# perhaps a block mode that yields the handle, closing on block exit?
|
||||||
|
MDBX::Database.open( 'database' ) do |db|
|
||||||
|
puts db[ 'key1' ]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access data
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
db[ 'key1' ] #=> val
|
||||||
|
# In the backend, automatically creates the snapshot, retrieves the value, and removes the snapshot before returning.
|
||||||
|
|
||||||
|
# read-only block
|
||||||
|
db.snapshot do
|
||||||
|
db[ 'key1' ] #=> val
|
||||||
|
...
|
||||||
|
end
|
||||||
|
# This is much faster for retrieving many values
|
||||||
|
|
||||||
|
# Maybe have a snapshot object that acts like a DB while it exists?
|
||||||
|
snap = db.snapshot
|
||||||
|
snap[ 'whatever' ] #=> data
|
||||||
|
snap.close
|
||||||
|
```
|
||||||
|
|
||||||
|
### Write data
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
db[ 'key1' ] = val
|
||||||
|
# In the backend, automatically creates a transaction, stores the value, and closes the transaction before returning.
|
||||||
|
|
||||||
|
# writable block
|
||||||
|
db.transaction do
|
||||||
|
db[ 'key1' ] = val
|
||||||
|
end
|
||||||
|
# Much faster for writing many values, should commit on success or abort on any exception
|
||||||
|
|
||||||
|
# Maybe have a transaction object that acts like a DB while it exists?
|
||||||
|
# ALL OTHER TRANSACTIONS will block until this is closed
|
||||||
|
txn = db.transaction
|
||||||
|
txn[ 'whatever' ] = data
|
||||||
|
txn.commit # or txn.abort
|
||||||
|
```
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
|
||||||
|
Identical interface to top-level databases. Just have to pull the collection first.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
collection = db.collection( 'stuff' ) # raise if nonexistent
|
||||||
|
# This now works just like the main db object
|
||||||
|
|
||||||
|
collection.transaction.do
|
||||||
|
...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cleaning up
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
db.close
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Stats!
|
||||||
|
|
||||||
|
|
||||||
|
TODO
|
||||||
|
-------
|
||||||
|
|
||||||
|
gem install mdbx -- --with-opt-dir=/usr/local
|
||||||
|
|
||||||
|
- [ ] Multiple value per key -- .insert, .delete? iterator for multi-val keys
|
||||||
|
- [ ] each_pair?
|
||||||
|
- [ ] document how serialization works
|
||||||
|
- [ ] document everything, really
|
||||||
|
- [x] transaction/snapshot blocks
|
||||||
|
- [ ] Arbitrary keys instead of forcing to strings?
|
||||||
|
- [ ] Disallow collection switching if there is an open transaction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ rmdbx_free( void *db )
|
||||||
{
|
{
|
||||||
if ( db ) {
|
if ( db ) {
|
||||||
rmdbx_close_all( db );
|
rmdbx_close_all( db );
|
||||||
free( db );
|
xfree( db );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ rmdbx_close( VALUE self )
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* db.closed? #=> false
|
* db.closed? => false
|
||||||
*
|
*
|
||||||
* Predicate: return true if the database environment is closed.
|
* Predicate: return true if the database environment is closed.
|
||||||
*/
|
*/
|
||||||
|
|
@ -102,7 +102,7 @@ rmdbx_closed_p( VALUE self )
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* db.in_transaction? #=> false
|
* db.in_transaction? => false
|
||||||
*
|
*
|
||||||
* Predicate: return true if a transaction (or snapshot)
|
* Predicate: return true if a transaction (or snapshot)
|
||||||
* is currently open.
|
* is currently open.
|
||||||
|
|
@ -117,6 +117,7 @@ rmdbx_in_transaction_p( VALUE self )
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Open the DB environment handle.
|
* Open the DB environment handle.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
VALUE
|
VALUE
|
||||||
rmdbx_open_env( VALUE self )
|
rmdbx_open_env( VALUE self )
|
||||||
|
|
@ -139,7 +140,7 @@ rmdbx_open_env( VALUE self )
|
||||||
mdbx_env_set_maxreaders( db->env, db->settings.max_readers );
|
mdbx_env_set_maxreaders( db->env, db->settings.max_readers );
|
||||||
|
|
||||||
/* Set an upper boundary (in bytes) for the database map size. */
|
/* Set an upper boundary (in bytes) for the database map size. */
|
||||||
if ( db->settings.max_size > -1 )
|
if ( db->settings.max_size )
|
||||||
mdbx_env_set_geometry( db->env, -1, -1, db->settings.max_size, -1, -1, -1 );
|
mdbx_env_set_geometry( db->env, -1, -1, db->settings.max_size, -1, -1, -1 );
|
||||||
|
|
||||||
rc = mdbx_env_open( db->env, db->path, db->settings.env_flags, db->settings.mode );
|
rc = mdbx_env_open( db->env, db->path, db->settings.env_flags, db->settings.mode );
|
||||||
|
|
@ -251,7 +252,9 @@ rmdbx_rb_closetxn( VALUE self, VALUE write )
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* db.clear
|
* db.clear
|
||||||
*
|
*
|
||||||
* Empty the database (or collection) on disk. Unrecoverable!
|
* 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!
|
||||||
*/
|
*/
|
||||||
VALUE
|
VALUE
|
||||||
rmdbx_clear( VALUE self )
|
rmdbx_clear( VALUE self )
|
||||||
|
|
@ -313,7 +316,7 @@ rmdbx_val_for( VALUE self, VALUE arg )
|
||||||
|
|
||||||
|
|
||||||
/* call-seq:
|
/* call-seq:
|
||||||
* db.keys #=> [ 'key1', 'key2', ... ]
|
* db.keys => [ 'key1', 'key2', ... ]
|
||||||
*
|
*
|
||||||
* Return an array of all keys in the current collection.
|
* Return an array of all keys in the current collection.
|
||||||
*/
|
*/
|
||||||
|
|
@ -351,7 +354,7 @@ rmdbx_keys( VALUE self )
|
||||||
|
|
||||||
|
|
||||||
/* call-seq:
|
/* call-seq:
|
||||||
* db[ 'key' ] #=> value
|
* db[ 'key' ] => value
|
||||||
*
|
*
|
||||||
* Convenience method: return a single value for +key+ immediately.
|
* Convenience method: return a single value for +key+ immediately.
|
||||||
*/
|
*/
|
||||||
|
|
@ -390,7 +393,7 @@ rmdbx_get_val( VALUE self, VALUE key )
|
||||||
|
|
||||||
|
|
||||||
/* call-seq:
|
/* call-seq:
|
||||||
* db[ 'key' ] = value #=> value
|
* db[ 'key' ] = value
|
||||||
*
|
*
|
||||||
* Convenience method: set a single value for +key+
|
* Convenience method: set a single value for +key+
|
||||||
*/
|
*/
|
||||||
|
|
@ -432,7 +435,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* db.statistics #=> (hash of stats)
|
* db.statistics => (hash of stats)
|
||||||
*
|
*
|
||||||
* Returns a hash populated with various metadata for the opened
|
* Returns a hash populated with various metadata for the opened
|
||||||
* database.
|
* database.
|
||||||
|
|
@ -449,17 +452,19 @@ rmdbx_stats( VALUE self )
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gets or sets the sub-database "collection" that read/write operations apply to.
|
* call-seq:
|
||||||
|
* 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.
|
* Passing +nil+ sets the database to the main, top-level namespace.
|
||||||
* If a block is passed, the collection automatically reverts to the
|
* If a block is passed, the collection automatically reverts to the
|
||||||
* prior collection when it exits.
|
* prior collection when it exits.
|
||||||
*
|
*
|
||||||
* db.collection( 'collection_name' ) # => db
|
|
||||||
* db.collection( nil ) # => db (main)
|
|
||||||
*
|
|
||||||
* db.collection( 'collection_name' ) do
|
* db.collection( 'collection_name' ) do
|
||||||
* [ ... ]
|
* [ ... ]
|
||||||
* end #=> reverts to the previous collection name
|
* end => reverts to the previous collection name
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
VALUE
|
VALUE
|
||||||
|
|
@ -475,8 +480,13 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
||||||
return rb_str_new_cstr( db->subdb );
|
return rb_str_new_cstr( db->subdb );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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." );
|
||||||
|
|
||||||
/* All transactions must be closed when switching database handles. */
|
/* All transactions must be closed when switching database handles. */
|
||||||
if ( db->txn ) rb_raise( rmdbx_eDatabaseError, "Unable to change collection: finish current transaction" );
|
if ( db->txn )
|
||||||
|
rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
|
||||||
|
|
||||||
/* Retain the prior database collection if a
|
/* Retain the prior database collection if a
|
||||||
* block was passed. */
|
* block was passed. */
|
||||||
|
|
@ -505,7 +515,7 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
|
||||||
db->subdb = prev_db;
|
db->subdb = prev_db;
|
||||||
rmdbx_close_dbi( db );
|
rmdbx_close_dbi( db );
|
||||||
}
|
}
|
||||||
free( prev_db );
|
xfree( prev_db );
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
@ -554,7 +564,7 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
|
||||||
db->settings.mode = 0644;
|
db->settings.mode = 0644;
|
||||||
db->settings.max_collections = 0;
|
db->settings.max_collections = 0;
|
||||||
db->settings.max_readers = 0;
|
db->settings.max_readers = 0;
|
||||||
db->settings.max_size = -1;
|
db->settings.max_size = 0;
|
||||||
|
|
||||||
/* Options setup, overrides.
|
/* Options setup, overrides.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,18 @@ Init_mdbx_ext()
|
||||||
{
|
{
|
||||||
rmdbx_mMDBX = rb_define_module( "MDBX" );
|
rmdbx_mMDBX = rb_define_module( "MDBX" );
|
||||||
|
|
||||||
/* The backend library version. */
|
|
||||||
VALUE version = rb_str_new_cstr( mdbx_version.git.describe );
|
VALUE version = rb_str_new_cstr( mdbx_version.git.describe );
|
||||||
|
/* The backend MDBX library version. */
|
||||||
rb_define_const( rmdbx_mMDBX, "LIBRARY_VERSION", version );
|
rb_define_const( rmdbx_mMDBX, "LIBRARY_VERSION", version );
|
||||||
|
|
||||||
|
/* A generic exception class for internal Database errors. */
|
||||||
rmdbx_eDatabaseError = rb_define_class_under( rmdbx_mMDBX, "DatabaseError", rb_eRuntimeError );
|
rmdbx_eDatabaseError = rb_define_class_under( rmdbx_mMDBX, "DatabaseError", rb_eRuntimeError );
|
||||||
rmdbx_eRollback = rb_define_class_under( rmdbx_mMDBX, "Rollback", rb_eRuntimeError );
|
|
||||||
|
/*
|
||||||
|
* Raising an MDBX::Rollback exception from within a transaction
|
||||||
|
* discards all changes and closes the transaction.
|
||||||
|
*/
|
||||||
|
rmdbx_eRollback = rb_define_class_under( rmdbx_mMDBX, "Rollback", rb_eRuntimeError );
|
||||||
|
|
||||||
rmdbx_init_database();
|
rmdbx_init_database();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
#include "mdbx.h"
|
#include "mdbx.h"
|
||||||
|
|
||||||
#ifndef MDBX_EXT_0_9_2
|
#ifndef MDBX_EXT_0_9_3
|
||||||
#define MDBX_EXT_0_9_2
|
#define MDBX_EXT_0_9_3
|
||||||
|
|
||||||
#define RMDBX_TXN_ROLLBACK 0
|
#define RMDBX_TXN_ROLLBACK 0
|
||||||
#define RMDBX_TXN_COMMIT 1
|
#define RMDBX_TXN_COMMIT 1
|
||||||
|
|
@ -63,5 +63,5 @@ extern void rmdbx_close_txn( rmdbx_db_t*, int );
|
||||||
extern VALUE rmdbx_gather_stats( rmdbx_db_t* );
|
extern VALUE rmdbx_gather_stats( rmdbx_db_t* );
|
||||||
|
|
||||||
|
|
||||||
#endif /* define MDBX_EXT_0_9_2 */
|
#endif /* define MDBX_EXT_0_9_3 */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ group( :development ) do
|
||||||
gem 'rspec', '~> 3.9'
|
gem 'rspec', '~> 3.9'
|
||||||
gem 'rubocop', '~> 0.93'
|
gem 'rubocop', '~> 0.93'
|
||||||
gem 'simplecov', '~> 0.12'
|
gem 'simplecov', '~> 0.12'
|
||||||
|
gem 'simplecov-console', '~> 0.9'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ require 'mdbx_ext'
|
||||||
module MDBX
|
module MDBX
|
||||||
|
|
||||||
# The version of this gem.
|
# The version of this gem.
|
||||||
#
|
|
||||||
# Note: the MDBX library version this gem was built
|
|
||||||
# against can be found in the 'LIBRARY_VERSION' constant.
|
|
||||||
#
|
|
||||||
VERSION = '0.0.1'
|
VERSION = '0.0.1'
|
||||||
|
|
||||||
end # module MDBX
|
end # module MDBX
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,89 @@
|
||||||
require 'mdbx' unless defined?( MDBX )
|
require 'mdbx' unless defined?( MDBX )
|
||||||
|
|
||||||
|
|
||||||
# TODO: rdoc
|
# The primary class for interacting with an MDBX database.
|
||||||
#
|
#
|
||||||
class MDBX::Database
|
class MDBX::Database
|
||||||
|
|
||||||
### Open an existing (or create a new) mdbx database at filesystem
|
### call-seq:
|
||||||
### +path+. In block form, the database is automatically closed.
|
### MDBX::Database.open( path ) => db
|
||||||
|
### MDBX::Database.open( path, options ) => db
|
||||||
|
###
|
||||||
|
### Open an existing (or create a new) mdbx database at filesystem
|
||||||
|
### +path+. In block form, the database is automatically closed
|
||||||
|
### when the block exits.
|
||||||
###
|
###
|
||||||
### MDBX::Database.open( path ) -> db
|
|
||||||
### MDBX::Database.open( path, options ) -> db
|
|
||||||
### MDBX::Database.open( path, options ) do |db|
|
### MDBX::Database.open( path, options ) do |db|
|
||||||
### db[ 'key' ] #=> value
|
### db[ 'key' ] = value
|
||||||
### end
|
### end
|
||||||
###
|
###
|
||||||
|
### Passing options modify various database behaviors. See the libmdbx
|
||||||
|
### documentation for detailed information.
|
||||||
|
###
|
||||||
|
### ==== Options
|
||||||
|
###
|
||||||
|
### [:mode]
|
||||||
|
### Whe creating a new database, set permissions to this 4 digit
|
||||||
|
### octal number. Defaults to `0644`. Set to `0` to never automatically
|
||||||
|
### create a new file, only opening existing databases.
|
||||||
|
###
|
||||||
|
### [:max_collections]
|
||||||
|
### Set the maximum number of "subdatabase" collections allowed. By
|
||||||
|
### default, collection support is disabled.
|
||||||
|
###
|
||||||
|
### [:max_readers]
|
||||||
|
### Set the maximum number of allocated simultaneous reader slots.
|
||||||
|
###
|
||||||
|
### [:max_size]
|
||||||
|
### Set an upper boundary (in bytes) for the database map size.
|
||||||
|
### The default is 10485760 bytes.
|
||||||
|
###
|
||||||
|
### [:nosubdir]
|
||||||
|
### When creating a new database, don't put the data and lock file
|
||||||
|
### under a dedicated subdirectory.
|
||||||
|
###
|
||||||
|
### [:readonly]
|
||||||
|
### Reject any write attempts while using this database handle.
|
||||||
|
###
|
||||||
|
### [:exclusive]
|
||||||
|
### Access is restricted to this process handle. Other attempts
|
||||||
|
### to use this database (even in readonly mode) are denied.
|
||||||
|
###
|
||||||
|
### [:compat]
|
||||||
|
### Avoid incompatibility errors when opening an in-use database with
|
||||||
|
### unknown or mismatched flag values.
|
||||||
|
###
|
||||||
|
### [:writemap]
|
||||||
|
### Trade safety for speed for databases that fit within available
|
||||||
|
### memory. (See MDBX documentation for details.)
|
||||||
|
###
|
||||||
|
### [:no_threadlocal]
|
||||||
|
### Parallelize read-only transactions across threads. Writes are
|
||||||
|
### always thread local. (See MDBX documentatoin for details.)
|
||||||
|
###
|
||||||
|
### [:no_readahead]
|
||||||
|
### Disable all use of OS readahead. Potentially useful for
|
||||||
|
### random reads wunder low memory conditions. Default behavior
|
||||||
|
### is to dynamically choose when to use or omit readahead.
|
||||||
|
###
|
||||||
|
### [:no_memory_init]
|
||||||
|
### Skip initializing malloc'ed memory to zeroes before writing.
|
||||||
|
###
|
||||||
|
### [:coalesce]
|
||||||
|
### Attempt to coalesce items for the garbage collector,
|
||||||
|
### potentialy increasing the chance of unallocating storage
|
||||||
|
### earlier.
|
||||||
|
###
|
||||||
|
### [:lifo_reclaim]
|
||||||
|
### Recycle garbage collected items via LIFO, instead of FIFO.
|
||||||
|
### Depending on underlying hardware (disk write-back cache), this
|
||||||
|
### could increase write performance.
|
||||||
|
###
|
||||||
|
### [:no_metasync]
|
||||||
|
### A system crash may sacrifice the last commit for a potentially
|
||||||
|
### large write performance increase. Database integrity is
|
||||||
|
### maintained.
|
||||||
|
###
|
||||||
def self::open( *args, &block )
|
def self::open( *args, &block )
|
||||||
db = new( *args )
|
db = new( *args )
|
||||||
|
|
||||||
|
|
@ -40,7 +110,7 @@ class MDBX::Database
|
||||||
private_class_method :new
|
private_class_method :new
|
||||||
|
|
||||||
|
|
||||||
# The options used to instantiate this database.
|
# Options used when instantiating this database handle.
|
||||||
attr_reader :options
|
attr_reader :options
|
||||||
|
|
||||||
# The path on disk of the database.
|
# The path on disk of the database.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,18 @@
|
||||||
|
|
||||||
if ENV[ 'COVERAGE' ]
|
if ENV[ 'COVERAGE' ]
|
||||||
require 'simplecov'
|
require 'simplecov'
|
||||||
SimpleCov.start
|
require 'simplecov-console'
|
||||||
|
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
||||||
|
SimpleCov::Formatter::HTMLFormatter,
|
||||||
|
SimpleCov::Formatter::Console,
|
||||||
|
])
|
||||||
|
SimpleCov.start do
|
||||||
|
add_filter 'spec'
|
||||||
|
# enable_coverage :branch
|
||||||
|
add_group "Needing tests" do |file|
|
||||||
|
file.covered_percent < 90
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@ require_relative '../lib/helper'
|
||||||
|
|
||||||
RSpec.describe( MDBX::Database ) do
|
RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
|
after( :all ) do
|
||||||
|
db = described_class.open( TEST_DATABASE.to_s )
|
||||||
|
db.clear
|
||||||
|
db.close
|
||||||
|
end
|
||||||
|
|
||||||
it "disallows direct calls to #new" do
|
it "disallows direct calls to #new" do
|
||||||
expect{ described_class.new }.
|
expect{ described_class.new }.
|
||||||
to raise_exception( NoMethodError, /private/ )
|
to raise_exception( NoMethodError, /private/ )
|
||||||
|
|
@ -60,8 +66,6 @@ RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
db[ 'test' ] = nil
|
db[ 'test' ] = nil
|
||||||
expect( db['test'] ).to be_nil
|
expect( db['test'] ).to be_nil
|
||||||
|
|
||||||
expect { db[ 'test' ] = nil }.to_not raise_exception
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can return an array of its keys" do
|
it "can return an array of its keys" do
|
||||||
|
|
@ -84,9 +88,9 @@ RSpec.describe( MDBX::Database ) do
|
||||||
it "fail if the max_collections option is not specified when opening" do
|
it "fail if the max_collections option is not specified when opening" do
|
||||||
db.close
|
db.close
|
||||||
db = described_class.open( TEST_DATABASE.to_s )
|
db = described_class.open( TEST_DATABASE.to_s )
|
||||||
db.collection( 'bucket' )
|
expect{
|
||||||
|
db.collection( 'bucket' )
|
||||||
expect{ db['key'] = true }.to raise_exception( /MDBX_DBS_FULL/ )
|
} .to raise_exception( /not enabled/ )
|
||||||
end
|
end
|
||||||
|
|
||||||
it "disallows regular key/val storage for namespace keys" do
|
it "disallows regular key/val storage for namespace keys" do
|
||||||
|
|
@ -130,5 +134,94 @@ RSpec.describe( MDBX::Database ) do
|
||||||
expect( db['bucket'] ).to be_nil
|
expect( db['bucket'] ).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
context 'transactions' do
|
||||||
|
|
||||||
|
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_collections: 5 ) }
|
||||||
|
|
||||||
|
after( :each ) do
|
||||||
|
db.close
|
||||||
|
end
|
||||||
|
|
||||||
|
it "knows when a transaction is currently open" do
|
||||||
|
expect( db.in_transaction? ).to be_falsey
|
||||||
|
db.snapshot
|
||||||
|
expect( db.in_transaction? ).to be_truthy
|
||||||
|
db.abort
|
||||||
|
expect( db.in_transaction? ).to be_falsey
|
||||||
|
|
||||||
|
db.snapshot do
|
||||||
|
expect( db.in_transaction? ).to be_truthy
|
||||||
|
end
|
||||||
|
expect( db.in_transaction? ).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "throws an error if changing collection mid-transaction" do
|
||||||
|
db.snapshot do
|
||||||
|
expect{ db.collection('nope') }.
|
||||||
|
to raise_exception( MDBX::DatabaseError, /transaction open/ )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "are re-entrant" do
|
||||||
|
3.times { db.snapshot }
|
||||||
|
expect( db.in_transaction? ).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "refuse to allow writes for read-only snapshots" do
|
||||||
|
db.snapshot
|
||||||
|
expect{ db[1] = true }.
|
||||||
|
to raise_exception( MDBX::DatabaseError, /permission denied/i )
|
||||||
|
end
|
||||||
|
|
||||||
|
it "revert changes via explicit rollbacks" do
|
||||||
|
db[ 1 ] = true
|
||||||
|
db.transaction
|
||||||
|
db[ 1 ] = false
|
||||||
|
expect( db[ 1 ] ).to be_falsey
|
||||||
|
db.rollback
|
||||||
|
expect( db[ 1 ] ).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "revert changes via uncaught exceptions" do
|
||||||
|
db[ 1 ] = true
|
||||||
|
expect {
|
||||||
|
db.transaction do
|
||||||
|
db[ 1 ] = false
|
||||||
|
raise "boom"
|
||||||
|
end
|
||||||
|
}.to raise_exception( RuntimeError, "boom" )
|
||||||
|
expect( db[ 1 ] ).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "revert changes via explicit exceptions" do
|
||||||
|
db[ 1 ] = true
|
||||||
|
expect {
|
||||||
|
db.transaction do
|
||||||
|
db[ 1 ] = false
|
||||||
|
raise MDBX::Rollback, "boom!"
|
||||||
|
end
|
||||||
|
}.to_not raise_exception
|
||||||
|
expect( db[ 1 ] ).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "write changes after commit" do
|
||||||
|
db[ 1 ] = true
|
||||||
|
db.transaction
|
||||||
|
db[ 1 ] = false
|
||||||
|
expect( db[ 1 ] ).to be_falsey
|
||||||
|
db.commit
|
||||||
|
expect( db[ 1 ] ).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "automatically write changes after block" do
|
||||||
|
db[ 1 ] = true
|
||||||
|
db.transaction do
|
||||||
|
db[ 1 ] = false
|
||||||
|
end
|
||||||
|
expect( db[ 1 ] ).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
require_relative '../lib/helper'
|
require_relative '../lib/helper'
|
||||||
|
|
||||||
|
|
||||||
RSpec.fdescribe( MDBX::Database ) do
|
RSpec.describe( MDBX::Database ) do
|
||||||
|
|
||||||
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_readers: 500 ) }
|
let!( :db ) { described_class.open( TEST_DATABASE.to_s, max_readers: 500 ) }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue