mdbx 0.1.0.pre.20201217111933 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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