mdbx 0.1.0.pre.20201217111933 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,12 +14,18 @@ Init_mdbx_ext()
14
14
  {
15
15
  rmdbx_mMDBX = rb_define_module( "MDBX" );
16
16
 
17
- /* The backend library version. */
18
17
  VALUE version = rb_str_new_cstr( mdbx_version.git.describe );
18
+ /* The backend MDBX library version. */
19
19
  rb_define_const( rmdbx_mMDBX, "LIBRARY_VERSION", version );
20
20
 
21
+ /* A generic exception class for internal Database errors. */
21
22
  rmdbx_eDatabaseError = rb_define_class_under( rmdbx_mMDBX, "DatabaseError", rb_eRuntimeError );
22
- rmdbx_eRollback = rb_define_class_under( rmdbx_mMDBX, "Rollback", rb_eRuntimeError );
23
+
24
+ /*
25
+ * Raising an MDBX::Rollback exception from within a transaction
26
+ * discards all changes and closes the transaction.
27
+ */
28
+ rmdbx_eRollback = rb_define_class_under( rmdbx_mMDBX, "Rollback", rb_eRuntimeError );
23
29
 
24
30
  rmdbx_init_database();
25
31
  }
@@ -4,9 +4,43 @@
4
4
 
5
5
  #include "mdbx.h"
6
6
 
7
- #ifndef MDBX_EXT_0_9_2
8
- #define MDBX_EXT_0_9_2
7
+ #ifndef MDBX_EXT_0_9_3
8
+ #define MDBX_EXT_0_9_3
9
9
 
10
+ #define RMDBX_TXN_ROLLBACK 0
11
+ #define RMDBX_TXN_COMMIT 1
12
+
13
+ /*
14
+ * A struct encapsulating an instance's DB
15
+ * state and settings.
16
+ */
17
+ struct rmdbx_db {
18
+ MDBX_env *env;
19
+ MDBX_dbi dbi;
20
+ MDBX_txn *txn;
21
+ MDBX_cursor *cursor;
22
+
23
+ struct {
24
+ int env_flags;
25
+ int mode;
26
+ int open;
27
+ int max_collections;
28
+ int max_readers;
29
+ uint64_t max_size;
30
+ } settings;
31
+
32
+ struct {
33
+ int open;
34
+ int retain_txn;
35
+ } state;
36
+
37
+ char *path;
38
+ char *subdb;
39
+ };
40
+ typedef struct rmdbx_db rmdbx_db_t;
41
+
42
+ static const rb_data_type_t rmdbx_db_data;
43
+ extern void rmdbx_free( void *db ); /* forward declaration for the allocator */
10
44
 
11
45
  /* ------------------------------------------------------------
12
46
  * Globals
@@ -23,7 +57,11 @@ extern VALUE rmdbx_eRollback;
23
57
  * ------------------------------------------------------------ */
24
58
  extern void Init_rmdbx ( void );
25
59
  extern void rmdbx_init_database ( void );
60
+ extern void rmdbx_open_txn( rmdbx_db_t*, int );
61
+ extern void rmdbx_close_txn( rmdbx_db_t*, int );
62
+
63
+ extern VALUE rmdbx_gather_stats( rmdbx_db_t* );
26
64
 
27
65
 
28
- #endif /* define MDBX_EXT_0_9_2 */
66
+ #endif /* define MDBX_EXT_0_9_3 */
29
67
 
@@ -0,0 +1,191 @@
1
+ /* vim: set noet sta sw=4 ts=4 :
2
+ *
3
+ * Expose a bunch of mdbx internals to ruby.
4
+ * This is all largely stolen from mdbx_stat.c.
5
+ *
6
+ * Entry point is rmdbx_stats() in database.c.
7
+ *
8
+ */
9
+
10
+ #include "mdbx_ext.h"
11
+
12
+
13
+ /*
14
+ * Metadata specific to the mdbx build.
15
+ */
16
+ void
17
+ rmdbx_gather_build_stats( VALUE stat )
18
+ {
19
+ rb_hash_aset( stat, ID2SYM(rb_intern("build_compiler")),
20
+ rb_str_new_cstr(mdbx_build.compiler) );
21
+ rb_hash_aset( stat, ID2SYM(rb_intern("build_flags")),
22
+ rb_str_new_cstr(mdbx_build.flags) );
23
+ rb_hash_aset( stat, ID2SYM(rb_intern("build_options")),
24
+ rb_str_new_cstr(mdbx_build.options) );
25
+ rb_hash_aset( stat, ID2SYM(rb_intern("build_target")),
26
+ rb_str_new_cstr(mdbx_build.target) );
27
+ return;
28
+ }
29
+
30
+
31
+ /*
32
+ * Metadata for the database file.
33
+ */
34
+ void
35
+ rmdbx_gather_datafile_stats(
36
+ VALUE environ,
37
+ MDBX_stat mstat,
38
+ MDBX_envinfo menvinfo )
39
+ {
40
+ VALUE datafile = rb_hash_new();
41
+ rb_hash_aset( environ, ID2SYM(rb_intern("datafile")), datafile );
42
+
43
+ rb_hash_aset( datafile, ID2SYM(rb_intern("size_current")),
44
+ INT2NUM(menvinfo.mi_geo.current) );
45
+ rb_hash_aset( datafile, ID2SYM(rb_intern("pages")),
46
+ INT2NUM(menvinfo.mi_geo.current / mstat.ms_psize) );
47
+
48
+ if ( menvinfo.mi_geo.lower != menvinfo.mi_geo.upper ) {
49
+ rb_hash_aset( datafile, ID2SYM(rb_intern("type")),
50
+ rb_str_new_cstr("dynamic") );
51
+ rb_hash_aset( datafile, ID2SYM(rb_intern("size_lower")),
52
+ INT2NUM( menvinfo.mi_geo.lower ) );
53
+ rb_hash_aset( datafile, ID2SYM(rb_intern("size_upper")),
54
+ LONG2FIX( menvinfo.mi_geo.upper ) );
55
+ rb_hash_aset( datafile, ID2SYM(rb_intern("growth_step")),
56
+ INT2NUM( menvinfo.mi_geo.grow ) );
57
+ rb_hash_aset( datafile, ID2SYM(rb_intern("shrink_threshold")),
58
+ INT2NUM( menvinfo.mi_geo.shrink ) );
59
+ }
60
+ else {
61
+ rb_hash_aset( datafile, ID2SYM(rb_intern("type")),
62
+ rb_str_new_cstr("fixed") );
63
+ }
64
+
65
+ return;
66
+ }
67
+
68
+
69
+ /*
70
+ * Metadata for the database environment.
71
+ */
72
+ void
73
+ rmdbx_gather_environment_stats(
74
+ VALUE stat,
75
+ MDBX_stat mstat,
76
+ MDBX_envinfo menvinfo )
77
+ {
78
+ VALUE environ = rb_hash_new();
79
+ rb_hash_aset( stat, ID2SYM(rb_intern("environment")), environ );
80
+
81
+ rb_hash_aset( environ, ID2SYM(rb_intern("pagesize")),
82
+ INT2NUM(mstat.ms_psize) );
83
+ rb_hash_aset( environ, ID2SYM(rb_intern("last_txnid")),
84
+ INT2NUM(menvinfo.mi_recent_txnid) );
85
+ rb_hash_aset( environ, ID2SYM(rb_intern("last_reader_txnid")),
86
+ INT2NUM(menvinfo.mi_latter_reader_txnid) );
87
+ rb_hash_aset( environ, ID2SYM(rb_intern("max_readers")),
88
+ INT2NUM(menvinfo.mi_maxreaders) );
89
+ rb_hash_aset( environ, ID2SYM(rb_intern("readers_in_use")),
90
+ INT2NUM(menvinfo.mi_numreaders) );
91
+
92
+ rmdbx_gather_datafile_stats( environ, mstat, menvinfo );
93
+
94
+ return;
95
+ }
96
+
97
+
98
+ /*
99
+ * Callback iterator for pulling each reader's current state.
100
+ * See: https://erthink.github.io/libmdbx/group__c__statinfo.html#gad1ab5cf54d4a9f7d4c2999078920e8b0
101
+ *
102
+ */
103
+ int
104
+ reader_list_callback(
105
+ void *ctx,
106
+ int num,
107
+ int slot,
108
+ mdbx_pid_t pid,
109
+ mdbx_tid_t thread,
110
+ uint64_t txnid,
111
+ uint64_t lag,
112
+ size_t bytes_used,
113
+ size_t bytes_retired )
114
+ {
115
+ VALUE reader = rb_hash_new();
116
+
117
+ rb_hash_aset( reader, ID2SYM(rb_intern("slot")),
118
+ INT2NUM( slot ) );
119
+ rb_hash_aset( reader, ID2SYM(rb_intern("pid")),
120
+ LONG2FIX( pid ) );
121
+ rb_hash_aset( reader, ID2SYM(rb_intern("thread")),
122
+ LONG2FIX( thread ) );
123
+ rb_hash_aset( reader, ID2SYM(rb_intern("txnid")),
124
+ LONG2FIX( txnid ) );
125
+ rb_hash_aset( reader, ID2SYM(rb_intern("lag")),
126
+ LONG2FIX( lag ) );
127
+ rb_hash_aset( reader, ID2SYM(rb_intern("bytes_used")),
128
+ LONG2FIX( bytes_used ) );
129
+ rb_hash_aset( reader, ID2SYM(rb_intern("bytes_retired")),
130
+ LONG2FIX( bytes_retired ) );
131
+
132
+ rb_ary_push( (VALUE)ctx, reader );
133
+
134
+ return 0;
135
+ }
136
+
137
+
138
+ /*
139
+ * Metadata for current reader slots.
140
+ * Initialize an array and populate it with each reader's statistics.
141
+ */
142
+ void
143
+ rmdbx_gather_reader_stats(
144
+ rmdbx_db_t *db,
145
+ VALUE stat,
146
+ MDBX_stat mstat,
147
+ MDBX_envinfo menvinfo )
148
+ {
149
+ VALUE readers = rb_ary_new();
150
+
151
+ mdbx_reader_list( db->env, reader_list_callback, (void*)readers );
152
+ rb_hash_aset( stat, ID2SYM(rb_intern("readers")), readers );
153
+
154
+ return;
155
+ }
156
+
157
+
158
+ /*
159
+ * Build and return a hash of various statistic/metadata
160
+ * for the open +db+ handle.
161
+ */
162
+ VALUE
163
+ rmdbx_gather_stats( rmdbx_db_t *db )
164
+ {
165
+ VALUE stat = rb_hash_new();
166
+
167
+ int rc;
168
+ MDBX_stat mstat;
169
+ MDBX_envinfo menvinfo;
170
+
171
+ rmdbx_gather_build_stats( stat );
172
+
173
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
174
+ rc = mdbx_env_info_ex( db->env, db->txn, &menvinfo, sizeof(menvinfo) );
175
+ if ( rc != MDBX_SUCCESS )
176
+ rb_raise( rmdbx_eDatabaseError, "mdbx_env_info_ex: (%d) %s", rc, mdbx_strerror(rc) );
177
+
178
+ rc = mdbx_env_stat_ex( db->env, db->txn, &mstat, sizeof(mstat) );
179
+ if ( rc != MDBX_SUCCESS )
180
+ rb_raise( rmdbx_eDatabaseError, "mdbx_env_stat_ex: (%d) %s", rc, mdbx_strerror(rc) );
181
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
182
+
183
+ rmdbx_gather_environment_stats( stat, mstat, menvinfo );
184
+ rmdbx_gather_reader_stats( db, stat, mstat, menvinfo );
185
+
186
+ /* TODO: database and subdatabase stats */
187
+
188
+ return stat;
189
+ }
190
+
191
+
data/lib/mdbx.rb CHANGED
@@ -10,11 +10,7 @@ require 'mdbx_ext'
10
10
  module MDBX
11
11
 
12
12
  # The version of this gem.
13
- #
14
- # Note: the MDBX library version this gem was built
15
- # against can be found in the 'LIBRARY_VERSION' constant.
16
- #
17
- VERSION = '0.0.1'
13
+ VERSION = '0.1.0'
18
14
 
19
15
  end # module MDBX
20
16
 
data/lib/mdbx/database.rb CHANGED
@@ -5,20 +5,91 @@
5
5
  require 'mdbx' unless defined?( MDBX )
6
6
 
7
7
 
8
- # TODO: rdoc
8
+ # The primary class for interacting with an MDBX database.
9
9
  #
10
10
  class MDBX::Database
11
11
 
12
+ ### call-seq:
13
+ ### MDBX::Database.open( path ) => db
14
+ ### MDBX::Database.open( path, options ) => db
15
+ ###
12
16
  ### Open an existing (or create a new) mdbx database at filesystem
13
- ### +path+. In block form, the database is automatically closed.
17
+ ### +path+. In block form, the database is automatically closed
18
+ ### when the block exits.
14
19
  ###
15
- ### MDBX::Database.open( path ) -> db
16
- ### MDBX::Database.open( path, options ) -> db
17
20
  ### MDBX::Database.open( path, options ) do |db|
18
- ### db[ 'key' ] #=> value
19
- ### end
21
+ ### db[ 'key' ] = value
22
+ ### end # closed!
23
+ ###
24
+ ### Passing options modify various database behaviors. See the libmdbx
25
+ ### documentation for detailed information.
26
+ ###
27
+ ### ==== Options
28
+ ###
29
+ ### Unless otherwise mentioned, option keys are symbols, and values
30
+ ### are boolean.
31
+ ###
32
+ ### [:mode]
33
+ ### Whe creating a new database, set permissions to this 4 digit
34
+ ### octal number. Defaults to `0644`. Set to `0` to never automatically
35
+ ### create a new file, only opening existing databases.
36
+ ###
37
+ ### [:max_collections]
38
+ ### Set the maximum number of "subdatabase" collections allowed. By
39
+ ### default, collection support is disabled.
40
+ ###
41
+ ### [:max_readers]
42
+ ### Set the maximum number of allocated simultaneous reader slots.
43
+ ###
44
+ ### [:max_size]
45
+ ### Set an upper boundary (in bytes) for the database map size.
46
+ ### The default is 10485760 bytes.
47
+ ###
48
+ ### [:nosubdir]
49
+ ### When creating a new database, don't put the data and lock file
50
+ ### under a dedicated subdirectory.
51
+ ###
52
+ ### [:readonly]
53
+ ### Reject any write attempts while using this database handle.
54
+ ###
55
+ ### [:exclusive]
56
+ ### Access is restricted to the first opening process. Other attempts
57
+ ### to use this database (even in readonly mode) are denied.
58
+ ###
59
+ ### [:compat]
60
+ ### Skip compatibility checks when opening an in-use database with
61
+ ### unknown or mismatched flag values.
62
+ ###
63
+ ### [:writemap]
64
+ ### Trade safety for speed for databases that fit within available
65
+ ### memory. (See MDBX documentation for details.)
20
66
  ###
21
- ### FIXME: options!
67
+ ### [:no_threadlocal]
68
+ ### Parallelize read-only transactions across threads. Writes are
69
+ ### always thread local. (See MDBX documentatoin for details.)
70
+ ###
71
+ ### [:no_readahead]
72
+ ### Disable all use of OS readahead. Potentially useful for
73
+ ### random reads wunder low memory conditions. Default behavior
74
+ ### is to dynamically choose when to use or omit readahead.
75
+ ###
76
+ ### [:no_memory_init]
77
+ ### Skip initializing malloc'ed memory to zeroes before writing.
78
+ ###
79
+ ### [:coalesce]
80
+ ### Attempt to coalesce items for the garbage collector,
81
+ ### potentialy increasing the chance of unallocating storage
82
+ ### earlier.
83
+ ###
84
+ ### [:lifo_reclaim]
85
+ ### Recycle garbage collected items via LIFO, instead of FIFO.
86
+ ### Depending on underlying hardware (disk write-back cache), this
87
+ ### could increase write performance.
88
+ ###
89
+ ### [:no_metasync]
90
+ ### A system crash may sacrifice the last commit for a potentially
91
+ ### large write performance increase. Database integrity is
92
+ ### maintained.
22
93
  ###
23
94
  def self::open( *args, &block )
24
95
  db = new( *args )
@@ -42,16 +113,18 @@ class MDBX::Database
42
113
  private_class_method :new
43
114
 
44
115
 
45
- # The options used to instantiate this database.
116
+ # Options used when instantiating this database handle.
46
117
  attr_reader :options
47
118
 
48
119
  # The path on disk of the database.
49
120
  attr_reader :path
50
121
 
51
122
  # A Proc for automatically serializing values.
123
+ # Defaults to +Marshal.dump+.
52
124
  attr_accessor :serializer
53
125
 
54
126
  # A Proc for automatically deserializing values.
127
+ # Defaults to +Marshal.load+.
55
128
  attr_accessor :deserializer
56
129
 
57
130
 
@@ -61,8 +134,200 @@ class MDBX::Database
61
134
  return self.collection( nil )
62
135
  end
63
136
 
64
- # Allow for some common nomenclature.
65
137
  alias_method :namespace, :collection
138
+ alias_method :size, :length
139
+ alias_method :each, :each_pair
140
+
141
+
142
+ #
143
+ # Transaction methods
144
+ #
145
+
146
+ ### Open a new mdbx read/write transaction. In block form,
147
+ ### the transaction is automatically committed when the block ends.
148
+ ###
149
+ ### Raising a MDBX::Rollback exception from within the block
150
+ ### automatically rolls the transaction back.
151
+ ###
152
+ def transaction( commit: true, &block )
153
+ self.open_transaction( commit )
154
+ yield self if block_given?
155
+
156
+ return self
157
+
158
+ rescue MDBX::Rollback
159
+ commit = false
160
+ self.rollback
161
+ rescue
162
+ commit = false
163
+ self.rollback
164
+ raise
165
+ ensure
166
+ if block_given?
167
+ commit ? self.commit : self.rollback
168
+ end
169
+ end
170
+
171
+
172
+ ### Open a new mdbx read only snapshot. In block form,
173
+ ### the snapshot is automatically closed when the block ends.
174
+ ###
175
+ def snapshot( &block )
176
+ self.transaction( commit: false, &block )
177
+ end
178
+
179
+
180
+ ### Close any open transaction, abandoning all changes.
181
+ ###
182
+ def rollback
183
+ return self.close_transaction( false )
184
+ end
185
+ alias_method :abort, :rollback
186
+
187
+
188
+ ### Close any open transaction, writing all changes.
189
+ ###
190
+ def commit
191
+ return self.close_transaction( true )
192
+ end
193
+ alias_method :save, :commit
194
+
195
+
196
+ #
197
+ # Hash-alike methods
198
+ #
199
+
200
+ ### Return the entirety of database contents as an Array of array
201
+ ### pairs.
202
+ ###
203
+ def to_a
204
+ self.snapshot do
205
+ return self.each_pair.to_a
206
+ end
207
+ end
208
+
209
+
210
+ ### Return the entirety of database contents as a Hash.
211
+ ###
212
+ def to_h
213
+ self.snapshot do
214
+ return self.each_pair.to_h
215
+ end
216
+ end
217
+
218
+
219
+ ### Returns +true+ if the current collection has no data.
220
+ ###
221
+ def empty?
222
+ return self.size.zero?
223
+ end
224
+
225
+
226
+ ### Returns the value for the given key, if found.
227
+ ### If key is not found and no block was given, returns nil.
228
+ ### If key is not found and a block was given, yields key to the
229
+ ### block and returns the block's return value.
230
+ ###
231
+ def fetch( key, &block )
232
+ val = self[ key ]
233
+ if block_given?
234
+ return block.call( key ) if val.nil?
235
+ else
236
+ return val if val
237
+ raise KeyError, "key not found: %p" % [ key ]
238
+ end
239
+ end
240
+
241
+
242
+ ### Deletes the entry for the given key and returns its associated
243
+ ### value. If no block is given and key is found, deletes the entry
244
+ ### and returns the associated value. If no block given and key is
245
+ ### not found, returns nil.
246
+ ###
247
+ ### If a block is given and key is found, ignores the block, deletes
248
+ ### the entry, and returns the associated value. If a block is given
249
+ ### and key is not found, calls the block and returns the block's
250
+ ### return value.
251
+ ###
252
+ def delete( key, &block )
253
+ val = self[ key ]
254
+ return block.call( key ) if block_given? && val.nil?
255
+
256
+ self[ key ] = nil
257
+ return val
258
+ end
259
+
260
+
261
+ ### Returns a new Array containing all keys in the collection.
262
+ ###
263
+ def keys
264
+ self.snapshot do
265
+ return self.each_key.to_a
266
+ end
267
+ end
268
+
269
+
270
+ ### Returns a new Hash object containing the entries for the given
271
+ ### keys. Any given keys that are not found are ignored.
272
+ ###
273
+ def slice( *keys )
274
+ self.snapshot do
275
+ return keys.each_with_object( {} ) do |key, acc|
276
+ val = self[ key ]
277
+ acc[ key ] = val if val
278
+ end
279
+ end
280
+ end
281
+
282
+
283
+ ### Returns a new Array containing all values in the collection.
284
+ ###
285
+ def values
286
+ self.snapshot do
287
+ return self.each_value.to_a
288
+ end
289
+ end
290
+
291
+
292
+ ### Returns a new Array containing values for the given +keys+.
293
+ ###
294
+ def values_at( *keys )
295
+ self.snapshot do
296
+ return keys.each_with_object( [] ) do |key, acc|
297
+ acc << self[ key ]
298
+ end
299
+ end
300
+ end
301
+
302
+
303
+ #
304
+ # Utility methods
305
+ #
306
+
307
+ ### Return a hash of various metadata for the current database.
308
+ ###
309
+ def statistics
310
+ raw = self.raw_stats
311
+
312
+ # Place build options in their own hash.
313
+ #
314
+ build_opts = raw.delete( :build_options ).split.each_with_object( {} ) do |opt, acc|
315
+ key, val = opt.split( '=' )
316
+ acc[ key.to_sym ] = Integer( val ) rescue val
317
+ end
318
+
319
+ stats = {
320
+ build: {
321
+ compiler: raw.delete( :build_compiler ),
322
+ flags: raw.delete( :build_flags ),
323
+ options: build_opts,
324
+ target: raw.delete( :build_target )
325
+ }
326
+ }
327
+ stats.merge!( raw )
328
+
329
+ return stats
330
+ end
66
331
 
67
332
  end # class MDBX::Database
68
333