mdbx 0.1.1 → 0.3.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e88f3e5ea2e66b7e924500a33f25f635c712e359bd0f8e19c52f60605bcafe46
4
- data.tar.gz: c069a2b838b86f1cc9873613ad1b99c1f888f24eab395c9f264d830aa7ba810d
3
+ metadata.gz: 678369c278e65d4faf56eb814e2cb7eba22da57fb5c07c6e4f9dc4e6edd7f09f
4
+ data.tar.gz: 0fc4b455b4556bb932f867fa40e7c240f555a5a6bbb7d09cb55c68ed7745bfd0
5
5
  SHA512:
6
- metadata.gz: 6d575ad77ff4c72382d13852d0d075c8b8caa664f8e55ace63e0e638695cd2cff43d2580484abff76fce0cf4efa60b8423d4330d65d1a1f39da07c8cd93ea7e9
7
- data.tar.gz: 895d6aee64d76e6e2b62071ae7d1cc98aa4d692d2b377c0d04e6f9f5ebb7495f23db7dafa66c8a2cdaf6a9a1925f003fca6d02e4646e4a23bf415167050dc0b4
6
+ metadata.gz: b9844be65fc6e46df8dbaee242ce9bf185c4330559d2a5d8c803eca43f4d09a783276c3d2a20258ef84be099a133031acf0f0b36ff0c2af1f69d0882a0dd4335
7
+ data.tar.gz: 22fd3c418db16d2a58100ad6bd8f3ae7b7ddce2172075713a798065440e59f6a2fd4cea47bd37d3585781dbeaebc0421f483e20375a5e652fe3a81ee76e0da2f
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -1,6 +1,68 @@
1
1
  # Release History for MDBX
2
2
 
3
3
  ---
4
+ ## v0.3.2 [2021-07-13] Mahlon E. Smith <mahlon@martini.nu>
5
+
6
+ Bugfixes:
7
+
8
+ - Fix double memory allocation during initialization.
9
+ - Make various ruby->c string allocations safe with the garbage collector.
10
+
11
+
12
+ Minutiae:
13
+
14
+ - Raise exception if instantiating with invalid options.
15
+
16
+
17
+ ---
18
+ ## v0.3.1 [2021-05-16] Mahlon E. Smith <mahlon@martini.nu>
19
+
20
+ Bugfix:
21
+
22
+ - #drop could potentially remove unintended data. Yanked version
23
+ v0.3.0.
24
+
25
+
26
+ ---
27
+ ## v0.3.0 [2021-04-09] Mahlon E. Smith <mahlon@martini.nu>
28
+
29
+ Enhancements:
30
+
31
+ - Alter the behavior of #clear, so it doesn't destroy collection
32
+ environments, but just empties them.
33
+
34
+ - Add #drop, which explictly -does- destroy a collection environment.
35
+
36
+ - Switching to a collection now automatically creates its environment.
37
+
38
+ - Add include? and has_key?, for presence checks without allocating
39
+ value memory or requiring deserialization.
40
+
41
+
42
+ Bugfixes:
43
+
44
+ - Run all cursor methods through rb_protect, to ensure proper
45
+ cursor cleanup in the event of an exception mid iteration.
46
+
47
+ - Fix the block form of collections to support multiple scopes.
48
+
49
+
50
+ ## v0.2.1 [2021-04-06] Mahlon E. Smith <mahlon@martini.nu>
51
+
52
+ Enhancement:
53
+
54
+ - Automatically stringify any argument to the collection() method.
55
+
56
+
57
+ ## v0.2.0 [2021-03-19] Mahlon E. Smith <mahlon@martini.nu>
58
+
59
+ Enhancement:
60
+
61
+ - Support dup/clone. This has limited use, as there can only
62
+ be one open handle per process, but implemented in the interests
63
+ of avoiding unexpected behavior.
64
+
65
+
4
66
  ## v0.1.1 [2021-03-14] Mahlon E. Smith <mahlon@martini.nu>
5
67
 
6
68
  Bugfix:
data/README.md CHANGED
@@ -340,13 +340,6 @@ information about the build environment, the database environment, and
340
340
  the currently connected clients.
341
341
 
342
342
 
343
- ## TODO
344
-
345
- - Expose more database/collection information to statistics
346
- - Support libmdbx multiple values per key DUPSORT via `put`, `get`
347
- Enumerators, and a 'value' argument for `delete`.
348
-
349
-
350
343
  ## Contributing
351
344
 
352
345
  You can check out the current development source with Mercurial via its
@@ -1,18 +1,16 @@
1
- /* vim: set noet sta sw=4 ts=4 : */
2
-
3
- #include "mdbx_ext.h"
4
-
5
- /* Shortcut for fetching current DB variables.
1
+ /* vim: set noet sta sw=4 ts=4 fdm=marker: */
2
+ /*
3
+ * Primary database handle functions.
4
+ *
6
5
  */
7
- #define UNWRAP_DB( val, db ) \
8
- rmdbx_db_t *db; \
9
- TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db );
10
6
 
7
+ #include "mdbx_ext.h"
11
8
 
12
9
  VALUE rmdbx_cDatabase;
13
10
 
11
+
14
12
  /*
15
- * Ruby allocation hook.
13
+ * Ruby data allocation wrapper.
16
14
  */
17
15
  static const rb_data_type_t rmdbx_db_data = {
18
16
  .wrap_struct_name = "MDBX::Database::Data",
@@ -27,11 +25,24 @@ static const rb_data_type_t rmdbx_db_data = {
27
25
  VALUE
28
26
  rmdbx_alloc( VALUE klass )
29
27
  {
30
- rmdbx_db_t *new = RB_ALLOC( rmdbx_db_t );
28
+ rmdbx_db_t *new;
31
29
  return TypedData_Make_Struct( klass, rmdbx_db_t, &rmdbx_db_data, new );
32
30
  }
33
31
 
34
32
 
33
+ /*
34
+ * Cleanup a previously allocated DB environment.
35
+ */
36
+ void
37
+ rmdbx_free( void *db )
38
+ {
39
+ if ( db ) {
40
+ rmdbx_close_all( db );
41
+ xfree( db );
42
+ }
43
+ }
44
+
45
+
35
46
  /*
36
47
  * Ensure all database file descriptors are collected and
37
48
  * removed.
@@ -62,19 +73,9 @@ rmdbx_close_dbi( rmdbx_db_t *db )
62
73
 
63
74
 
64
75
  /*
65
- * Cleanup a previously allocated DB environment.
66
- */
67
- void
68
- rmdbx_free( void *db )
69
- {
70
- if ( db ) {
71
- rmdbx_close_all( db );
72
- xfree( db );
73
- }
74
- }
75
-
76
-
77
- /*
76
+ * call-seq:
77
+ * db.close => true
78
+ *
78
79
  * Cleanly close an opened database.
79
80
  */
80
81
  VALUE
@@ -101,17 +102,63 @@ rmdbx_closed_p( VALUE self )
101
102
 
102
103
 
103
104
  /*
104
- * call-seq:
105
- * db.in_transaction? => false
105
+ * Check if a given +flag+ is enabled for flag +val+.
106
+ */
107
+ int
108
+ rmdbx_flag_enabled( val, flag )
109
+ {
110
+ return ( val & flag ) == flag;
111
+ }
112
+
113
+
114
+ /*
115
+ * Given a ruby string +key+ and a pointer to an MDBX_val, prepare the
116
+ * key for usage within mdbx. All keys are explicitly converted to
117
+ * strings.
106
118
  *
107
- * Predicate: return true if a transaction (or snapshot)
108
- * is currently open.
119
+ */
120
+ void
121
+ rmdbx_key_for( VALUE key, MDBX_val *ckey )
122
+ {
123
+ VALUE key_str = rb_funcall( key, rb_intern("to_s"), 0 );
124
+ ckey->iov_len = RSTRING_LEN( key_str );
125
+ ckey->iov_base = malloc( ckey->iov_len );
126
+ strlcpy( ckey->iov_base, StringValuePtr(key_str), ckey->iov_len + 1 );
127
+ }
128
+
129
+
130
+ /*
131
+ * Given a ruby +value+ and a pointer to an MDBX_val, prepare
132
+ * the value for usage within mdbx. Values are potentially serialized.
133
+ *
134
+ */
135
+ void
136
+ rmdbx_val_for( VALUE self, VALUE val, MDBX_val *data )
137
+ {
138
+ VALUE serialize_proc = rb_iv_get( self, "@serializer" );
139
+
140
+ if ( ! NIL_P( serialize_proc ) )
141
+ val = rb_funcall( serialize_proc, rb_intern("call"), 1, val );
142
+
143
+ Check_Type( val, T_STRING );
144
+
145
+ data->iov_len = RSTRING_LEN( val );
146
+ data->iov_base = malloc( data->iov_len );
147
+ strlcpy( data->iov_base, StringValuePtr(val), data->iov_len + 1 );
148
+ }
149
+
150
+
151
+ /*
152
+ * Deserialize and return a value.
109
153
  */
110
154
  VALUE
111
- rmdbx_in_transaction_p( VALUE self )
155
+ rmdbx_deserialize( VALUE self, VALUE val )
112
156
  {
113
- UNWRAP_DB( self, db );
114
- return db->txn ? Qtrue : Qfalse;
157
+ VALUE deserialize_proc = rb_iv_get( self, "@deserializer" );
158
+ if ( ! NIL_P( deserialize_proc ) )
159
+ val = rb_funcall( deserialize_proc, rb_intern("call"), 1, val );
160
+
161
+ return val;
115
162
  }
116
163
 
117
164
 
@@ -148,138 +195,65 @@ rmdbx_open_env( VALUE self )
148
195
  rmdbx_close_all( db );
149
196
  rb_raise( rmdbx_eDatabaseError, "mdbx_env_open: (%d) %s", rc, mdbx_strerror(rc) );
150
197
  }
151
- db->state.open = 1;
152
198
 
199
+ db->state.open = 1;
153
200
  return Qtrue;
154
201
  }
155
202
 
156
203
 
157
- /*
158
- * Open a cursor for iteration.
159
- */
160
- void
161
- rmdbx_open_cursor( rmdbx_db_t *db )
162
- {
163
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
164
- if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
165
-
166
- int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
167
- if ( rc != MDBX_SUCCESS ) {
168
- rmdbx_close_all( db );
169
- rb_raise( rmdbx_eDatabaseError, "Unable to open cursor: (%d) %s", rc, mdbx_strerror(rc) );
170
- }
171
-
172
- return;
173
- }
174
-
175
-
176
- /*
177
- * Open a new database transaction. If a transaction is already
178
- * open, this is a no-op.
179
- *
180
- * +rwflag+ must be either MDBX_TXN_RDONLY or MDBX_TXN_READWRITE.
181
- */
182
- void
183
- rmdbx_open_txn( rmdbx_db_t *db, int rwflag )
184
- {
185
- if ( db->txn ) return;
186
-
187
- int rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn );
188
- if ( rc != MDBX_SUCCESS ) {
189
- rmdbx_close_all( db );
190
- rb_raise( rmdbx_eDatabaseError, "mdbx_txn_begin: (%d) %s", rc, mdbx_strerror(rc) );
191
- }
192
-
193
- if ( db->dbi == 0 ) {
194
- // FIXME: dbi_flags
195
- rc = mdbx_dbi_open( db->txn, db->subdb, MDBX_CREATE, &db->dbi );
196
- if ( rc != MDBX_SUCCESS ) {
197
- rmdbx_close_all( db );
198
- rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
199
- }
200
- }
201
-
202
- return;
203
- }
204
-
205
-
206
- /*
207
- * Close any existing database transaction. If there is no
208
- * active transaction, this is a no-op. If there is a long
209
- * running transaction open, this is a no-op.
210
- *
211
- * +txnflag must either be RMDBX_TXN_ROLLBACK or RMDBX_TXN_COMMIT.
212
- */
213
- void
214
- rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
215
- {
216
- if ( ! db->txn || db->state.retain_txn > -1 ) return;
217
-
218
- switch ( txnflag ) {
219
- case RMDBX_TXN_COMMIT:
220
- mdbx_txn_commit( db->txn );
221
- default:
222
- mdbx_txn_abort( db->txn );
223
- }
224
-
225
- db->txn = 0;
226
- return;
227
- }
228
-
229
-
230
204
  /*
231
205
  * call-seq:
232
- * db.open_transaction( mode )
233
- *
234
- * Open a new long-running transaction. If +mode+ is true,
235
- * it is opened read/write.
206
+ * db.clear
236
207
  *
208
+ * Empty the current collection on disk. If collections are not enabled
209
+ * or the database handle is set to the top-level (main) db - this
210
+ * deletes *all records* from the database.
237
211
  */
238
212
  VALUE
239
- rmdbx_rb_opentxn( VALUE self, VALUE mode )
213
+ rmdbx_clear( VALUE self )
240
214
  {
241
215
  UNWRAP_DB( self, db );
242
216
 
243
- rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
244
- db->state.retain_txn = RTEST(mode) ? 1 : 0;
217
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
218
+ int rc = mdbx_drop( db->txn, db->dbi, false );
245
219
 
246
- return Qtrue;
220
+ if ( rc != MDBX_SUCCESS )
221
+ rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
222
+
223
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
224
+
225
+ return Qnil;
247
226
  }
248
227
 
249
228
 
250
229
  /*
251
230
  * call-seq:
252
- * db.close_transaction( mode )
253
- *
254
- * Close a long-running transaction. If +write+ is true,
255
- * the transaction is committed. Otherwise, rolled back.
231
+ * db.drop( collection ) -> db
256
232
  *
233
+ * Destroy a collection. You must be in the top level database to call
234
+ * this method.
257
235
  */
258
236
  VALUE
259
- rmdbx_rb_closetxn( VALUE self, VALUE write )
237
+ rmdbx_drop( VALUE self, VALUE name )
260
238
  {
261
239
  UNWRAP_DB( self, db );
262
240
 
263
- db->state.retain_txn = -1;
264
- rmdbx_close_txn( db, RTEST(write) ? RMDBX_TXN_COMMIT : RMDBX_TXN_ROLLBACK );
241
+ /* Provide a friendlier error message if max_collections is 0. */
242
+ if ( db->settings.max_collections == 0 )
243
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: collections are not enabled." );
265
244
 
266
- return Qtrue;
267
- }
245
+ /* All transactions must be closed when dropping a database. */
246
+ if ( db->txn )
247
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: transaction open" );
268
248
 
249
+ /* A drop can only be performed from the top-level database. */
250
+ if ( db->subdb != NULL )
251
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: switch to top-level db first" );
269
252
 
270
- /*
271
- * call-seq:
272
- * db.clear
273
- *
274
- * Empty the current collection on disk. If collections are not enabled
275
- * or the database handle is set to the top-level (main) db - this
276
- * deletes *all records* from the database. This is not recoverable!
277
- */
278
- VALUE
279
- rmdbx_clear( VALUE self )
280
- {
281
- UNWRAP_DB( self, db );
253
+ name = rb_funcall( name, rb_intern("to_s"), 0 );
254
+ db->subdb = StringValueCStr( name );
282
255
 
256
+ rmdbx_close_dbi( db ); /* ensure we're reopening within the new subdb */
283
257
  rmdbx_open_txn( db, MDBX_TXN_READWRITE );
284
258
  int rc = mdbx_drop( db->txn, db->dbi, true );
285
259
 
@@ -288,345 +262,478 @@ rmdbx_clear( VALUE self )
288
262
 
289
263
  rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
290
264
 
291
- /* Refresh the environment handles. */
292
- rmdbx_open_env( self );
265
+ /* Reset the current collection to the top level. */
266
+ db->subdb = NULL;
267
+ rmdbx_close_dbi( db ); /* ensure next access is not in the defunct subdb */
293
268
 
294
- return Qnil;
269
+ return self;
295
270
  }
296
271
 
297
272
 
298
- /*
299
- * Given a ruby +arg+, convert and return a structure
300
- * suitable for usage as a key for mdbx. All keys are explicitly
301
- * converted to strings.
273
+ /* call-seq:
274
+ * db.length -> Integer
275
+ *
276
+ * Returns the count of keys in the currently selected collection.
302
277
  */
303
- MDBX_val
304
- rmdbx_key_for( VALUE arg )
278
+ VALUE
279
+ rmdbx_length( VALUE self )
305
280
  {
306
- MDBX_val rv;
281
+ UNWRAP_DB( self, db );
282
+ MDBX_stat mstat;
283
+
284
+ CHECK_HANDLE();
285
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
286
+
287
+ int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
288
+ if ( rc != MDBX_SUCCESS )
289
+ rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_stat: (%d) %s", rc, mdbx_strerror(rc) );
307
290
 
308
- arg = rb_funcall( arg, rb_intern("to_s"), 0 );
309
- rv.iov_len = RSTRING_LEN( arg );
310
- rv.iov_base = StringValuePtr( arg );
291
+ VALUE rv = LONG2FIX( mstat.ms_entries );
292
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
311
293
 
312
294
  return rv;
313
295
  }
314
296
 
315
297
 
316
- /*
317
- * Given a ruby +arg+, convert and return a structure
318
- * suitable for usage as a value for mdbx.
298
+ /* call-seq:
299
+ * db.include?( 'key' ) => bool
300
+ *
301
+ * Returns true if the current collection contains +key+.
319
302
  */
320
- MDBX_val
321
- rmdbx_val_for( VALUE self, VALUE arg )
303
+ VALUE
304
+ rmdbx_include( VALUE self, VALUE key )
322
305
  {
323
- MDBX_val rv;
324
- VALUE serialize_proc;
306
+ UNWRAP_DB( self, db );
325
307
 
326
- serialize_proc = rb_iv_get( self, "@serializer" );
327
- if ( ! NIL_P( serialize_proc ) )
328
- arg = rb_funcall( serialize_proc, rb_intern("call"), 1, arg );
308
+ CHECK_HANDLE();
309
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
329
310
 
330
- rv.iov_len = RSTRING_LEN( arg );
331
- rv.iov_base = StringValuePtr( arg );
311
+ MDBX_val ckey;
312
+ MDBX_val data;
313
+ rmdbx_key_for( key, &ckey );
332
314
 
333
- return rv;
334
- }
315
+ int rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
316
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
317
+ xfree( ckey.iov_base );
335
318
 
319
+ switch ( rc ) {
320
+ case MDBX_SUCCESS:
321
+ return Qtrue;
336
322
 
337
- /*
338
- * Deserialize and return a value.
339
- */
340
- VALUE
341
- rmdbx_deserialize( VALUE self, VALUE val )
342
- {
343
- VALUE deserialize_proc = rb_iv_get( self, "@deserializer" );
344
- if ( ! NIL_P( deserialize_proc ) )
345
- val = rb_funcall( deserialize_proc, rb_intern("call"), 1, val );
323
+ case MDBX_NOTFOUND:
324
+ return Qfalse;
346
325
 
347
- return val;
326
+ default:
327
+ rmdbx_close( self );
328
+ rb_raise( rmdbx_eDatabaseError, "Unable to fetch key: (%d) %s", rc, mdbx_strerror(rc) );
329
+ }
348
330
  }
349
331
 
350
332
 
351
333
  /* call-seq:
352
- * db.each_key {|key| block } => self
334
+ * db[ 'key' ] => value
353
335
  *
354
- * Calls the block once for each key, returning self.
355
- * A transaction must be opened prior to use.
336
+ * Return a single value for +key+ immediately.
356
337
  */
357
338
  VALUE
358
- rmdbx_each_key( VALUE self )
339
+ rmdbx_get_val( VALUE self, VALUE key )
359
340
  {
360
341
  UNWRAP_DB( self, db );
361
- MDBX_val key, data;
362
-
363
- rmdbx_open_cursor( db );
364
- RETURN_ENUMERATOR( self, 0, 0 );
365
342
 
366
- if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
367
- rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
368
- while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
369
- rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
370
- }
371
- }
343
+ CHECK_HANDLE();
344
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
372
345
 
373
- mdbx_cursor_close( db->cursor );
374
- db->cursor = NULL;
375
- return self;
376
- }
346
+ MDBX_val ckey;
347
+ MDBX_val data;
377
348
 
349
+ rmdbx_key_for( key, &ckey );
350
+ int rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
351
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
352
+ xfree( ckey.iov_base );
378
353
 
379
- /* call-seq:
380
- * db.each_value {|value| block } => self
354
+ VALUE rv;
355
+ switch ( rc ) {
356
+ case MDBX_SUCCESS:
357
+ rv = rb_str_new( data.iov_base, data.iov_len );
358
+ return rmdbx_deserialize( self, rv );
359
+
360
+ case MDBX_NOTFOUND:
361
+ return Qnil;
362
+
363
+ default:
364
+ rmdbx_close( self );
365
+ rb_raise( rmdbx_eDatabaseError, "Unable to fetch value: (%d) %s", rc, mdbx_strerror(rc) );
366
+ }
367
+ }
368
+
369
+
370
+ /* call-seq:
371
+ * db[ 'key' ] = value
381
372
  *
382
- * Calls the block once for each value, returning self.
383
- * A transaction must be opened prior to use.
373
+ * Set a single value for +key+. If the value is +nil+, the
374
+ * key is removed.
384
375
  */
385
376
  VALUE
386
- rmdbx_each_value( VALUE self )
377
+ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
387
378
  {
379
+ int rc;
388
380
  UNWRAP_DB( self, db );
389
- MDBX_val key, data;
390
381
 
391
- rmdbx_open_cursor( db );
392
- RETURN_ENUMERATOR( self, 0, 0 );
382
+ CHECK_HANDLE();
383
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
393
384
 
394
- if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
395
- VALUE rv = rb_str_new( data.iov_base, data.iov_len );
396
- rb_yield( rmdbx_deserialize( self, rv ) );
385
+ MDBX_val ckey;
386
+ rmdbx_key_for( key, &ckey );
397
387
 
398
- while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
399
- rv = rb_str_new( data.iov_base, data.iov_len );
400
- rb_yield( rmdbx_deserialize( self, rv ) );
401
- }
388
+ if ( NIL_P(val) ) { /* remove if set to nil */
389
+ rc = mdbx_del( db->txn, db->dbi, &ckey, NULL );
390
+ }
391
+ else {
392
+ MDBX_val old;
393
+ MDBX_val data;
394
+ rmdbx_val_for( self, val, &data );
395
+ rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
396
+ xfree( data.iov_base );
402
397
  }
403
398
 
404
- mdbx_cursor_close( db->cursor );
405
- db->cursor = NULL;
406
- return self;
399
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
400
+ xfree( ckey.iov_base );
401
+
402
+ switch ( rc ) {
403
+ case MDBX_SUCCESS:
404
+ return val;
405
+ case MDBX_NOTFOUND:
406
+ return Qnil;
407
+ default:
408
+ rb_raise( rmdbx_eDatabaseError, "Unable to update value: (%d) %s", rc, mdbx_strerror(rc) );
409
+ }
407
410
  }
408
411
 
409
412
 
410
- /* call-seq:
411
- * db.each_pair {|key, value| block } => self
413
+ /*
414
+ * Return the currently selected collection, or +nil+ if at the
415
+ * top-level.
416
+ */
417
+ VALUE
418
+ rmdbx_get_subdb( VALUE self )
419
+ {
420
+ UNWRAP_DB( self, db );
421
+ return ( db->subdb == NULL ) ? Qnil : rb_str_new_cstr( db->subdb );
422
+ }
423
+
424
+
425
+ /*
426
+ * Sets the current collection name for read/write operations.
412
427
  *
413
- * Calls the block once for each key and value, returning self.
414
- * A transaction must be opened prior to use.
415
428
  */
416
429
  VALUE
417
- rmdbx_each_pair( VALUE self )
430
+ rmdbx_set_subdb( VALUE self, VALUE name )
418
431
  {
419
432
  UNWRAP_DB( self, db );
420
- MDBX_val key, data;
421
433
 
422
- rmdbx_open_cursor( db );
423
- RETURN_ENUMERATOR( self, 0, 0 );
434
+ /* Provide a friendlier error message if max_collections is 0. */
435
+ if ( db->settings.max_collections == 0 )
436
+ rb_raise( rmdbx_eDatabaseError, "Unable to change collection: collections are not enabled." );
424
437
 
425
- if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
426
- VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
427
- VALUE rval = rb_str_new( data.iov_base, data.iov_len );
428
- rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
438
+ /* All transactions must be closed when switching database handles. */
439
+ if ( db->txn )
440
+ rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
429
441
 
430
- while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
431
- rkey = rb_str_new( key.iov_base, key.iov_len );
432
- rval = rb_str_new( data.iov_base, data.iov_len );
433
- rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
434
- }
442
+ xfree( db->subdb );
443
+ db->subdb = NULL;
444
+
445
+ if ( ! NIL_P(name) ) {
446
+ size_t len = RSTRING_LEN( name ) + 1;
447
+ db->subdb = malloc( len );
448
+ strlcpy( db->subdb, StringValuePtr(name), len );
435
449
  }
436
450
 
437
- mdbx_cursor_close( db->cursor );
438
- db->cursor = NULL;
451
+ /* Reset the db handle and issue a single transaction to reify
452
+ the collection.
453
+ */
454
+ rmdbx_close_dbi( db );
455
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
456
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
457
+
439
458
  return self;
440
459
  }
441
460
 
442
461
 
443
- /* call-seq:
444
- * db.length -> Integer
462
+ /*
463
+ * call-seq:
464
+ * db.in_transaction? => false
445
465
  *
446
- * Returns the count of keys in the currently selected collection.
466
+ * Predicate: return true if a transaction (or snapshot)
467
+ * is currently open.
447
468
  */
448
469
  VALUE
449
- rmdbx_length( VALUE self )
470
+ rmdbx_in_transaction_p( VALUE self )
450
471
  {
451
472
  UNWRAP_DB( self, db );
452
- MDBX_stat mstat;
473
+ return db->txn ? Qtrue : Qfalse;
474
+ }
453
475
 
454
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
455
- rmdbx_open_txn( db, MDBX_TXN_RDONLY );
456
476
 
457
- int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
458
- if ( rc != MDBX_SUCCESS )
459
- rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_stat: (%d) %s", rc, mdbx_strerror(rc) );
477
+ /*
478
+ * Open a new database transaction. If a transaction is already
479
+ * open, this is a no-op.
480
+ *
481
+ * +rwflag+ must be either MDBX_TXN_RDONLY or MDBX_TXN_READWRITE.
482
+ */
483
+ void
484
+ rmdbx_open_txn( rmdbx_db_t *db, int rwflag )
485
+ {
486
+ if ( db->txn ) return;
460
487
 
461
- VALUE rv = LONG2FIX( mstat.ms_entries );
462
- rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
488
+ int rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn );
489
+ if ( rc != MDBX_SUCCESS ) {
490
+ rmdbx_close_all( db );
491
+ rb_raise( rmdbx_eDatabaseError, "mdbx_txn_begin: (%d) %s", rc, mdbx_strerror(rc) );
492
+ }
463
493
 
464
- return rv;
494
+ if ( db->dbi == 0 ) {
495
+ rc = mdbx_dbi_open( db->txn, db->subdb, db->settings.db_flags, &db->dbi );
496
+ if ( rc != MDBX_SUCCESS ) {
497
+ rmdbx_close_all( db );
498
+ rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
499
+ }
500
+ }
501
+
502
+ return;
465
503
  }
466
504
 
467
505
 
468
- /* call-seq:
469
- * db[ 'key' ] => value
506
+ /*
507
+ * Close any existing database transaction. If there is no
508
+ * active transaction, this is a no-op. If there is a long
509
+ * running transaction open, this is a no-op.
510
+ *
511
+ * +txnflag must either be RMDBX_TXN_ROLLBACK or RMDBX_TXN_COMMIT.
512
+ */
513
+ void
514
+ rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
515
+ {
516
+ if ( ! db->txn || db->state.retain_txn > -1 ) return;
517
+
518
+ if ( txnflag == RMDBX_TXN_COMMIT ) {
519
+ mdbx_txn_commit( db->txn );
520
+ }
521
+ else {
522
+ mdbx_txn_abort( db->txn );
523
+ }
524
+
525
+ db->txn = 0;
526
+ return;
527
+ }
528
+
529
+
530
+ /*
531
+ * call-seq:
532
+ * db.open_transaction( mode )
533
+ *
534
+ * Open a new long-running transaction. If +mode+ is true,
535
+ * it is opened read/write.
470
536
  *
471
- * Return a single value for +key+ immediately.
472
537
  */
473
538
  VALUE
474
- rmdbx_get_val( VALUE self, VALUE key )
539
+ rmdbx_rb_opentxn( VALUE self, VALUE mode )
475
540
  {
476
- int rc;
477
541
  UNWRAP_DB( self, db );
542
+ CHECK_HANDLE();
478
543
 
479
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
480
- rmdbx_open_txn( db, MDBX_TXN_RDONLY );
544
+ rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
545
+ db->state.retain_txn = RTEST(mode) ? 1 : 0;
481
546
 
482
- MDBX_val ckey = rmdbx_key_for( key );
483
- MDBX_val data;
484
- VALUE rv;
485
- rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
486
- rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
547
+ return Qtrue;
548
+ }
487
549
 
488
- switch ( rc ) {
489
- case MDBX_SUCCESS:
490
- rv = rb_str_new( data.iov_base, data.iov_len );
491
- return rmdbx_deserialize( self, rv );
492
550
 
493
- case MDBX_NOTFOUND:
494
- return Qnil;
551
+ /*
552
+ * call-seq:
553
+ * db.close_transaction( mode )
554
+ *
555
+ * Close a long-running transaction. If +write+ is true,
556
+ * the transaction is committed. Otherwise, rolled back.
557
+ *
558
+ */
559
+ VALUE
560
+ rmdbx_rb_closetxn( VALUE self, VALUE write )
561
+ {
562
+ UNWRAP_DB( self, db );
495
563
 
496
- default:
497
- rmdbx_close( self );
498
- rb_raise( rmdbx_eDatabaseError, "Unable to fetch value: (%d) %s", rc, mdbx_strerror(rc) );
564
+ db->state.retain_txn = -1;
565
+ rmdbx_close_txn( db, RTEST(write) ? RMDBX_TXN_COMMIT : RMDBX_TXN_ROLLBACK );
566
+
567
+ return Qtrue;
568
+ }
569
+
570
+
571
+ /*
572
+ * Open a cursor for iteration.
573
+ */
574
+ void
575
+ rmdbx_open_cursor( rmdbx_db_t *db )
576
+ {
577
+ CHECK_HANDLE();
578
+ if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
579
+
580
+ int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
581
+ if ( rc != MDBX_SUCCESS ) {
582
+ rmdbx_close_all( db );
583
+ rb_raise( rmdbx_eDatabaseError, "Unable to open cursor: (%d) %s", rc, mdbx_strerror(rc) );
499
584
  }
585
+
586
+ return;
587
+ }
588
+
589
+
590
+ /*
591
+ * Enumerate over keys for the current collection.
592
+ */
593
+ VALUE
594
+ rmdbx_each_key_i( VALUE self )
595
+ {
596
+ UNWRAP_DB( self, db );
597
+ MDBX_val key, data;
598
+
599
+ if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
600
+ rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
601
+ while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
602
+ rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
603
+ }
604
+ }
605
+
606
+ return self;
500
607
  }
501
608
 
502
609
 
503
610
  /* call-seq:
504
- * db[ 'key' ] = value
611
+ * db.each_key {|key| block } => self
505
612
  *
506
- * Set a single value for +key+.
613
+ * Calls the block once for each key, returning self.
614
+ * A transaction must be opened prior to use.
507
615
  */
508
616
  VALUE
509
- rmdbx_put_val( VALUE self, VALUE key, VALUE val )
617
+ rmdbx_each_key( VALUE self )
510
618
  {
511
- int rc;
512
619
  UNWRAP_DB( self, db );
620
+ int state;
513
621
 
514
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
515
- rmdbx_open_txn( db, MDBX_TXN_READWRITE );
622
+ CHECK_HANDLE();
623
+ rmdbx_open_cursor( db );
624
+ RETURN_ENUMERATOR( self, 0, 0 );
516
625
 
517
- MDBX_val ckey = rmdbx_key_for( key );
626
+ rb_protect( rmdbx_each_key_i, self, &state );
518
627
 
519
- // FIXME: DUPSORT is enabled -- different api?
520
- // See: MDBX_NODUPDATA / MDBX_NOOVERWRITE
521
- if ( NIL_P(val) ) { /* remove if set to nil */
522
- rc = mdbx_del( db->txn, db->dbi, &ckey, NULL );
523
- }
524
- else {
525
- MDBX_val old;
526
- MDBX_val data = rmdbx_val_for( self, val );
527
- rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
528
- }
628
+ mdbx_cursor_close( db->cursor );
629
+ db->cursor = NULL;
529
630
 
530
- rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
631
+ if ( state ) rb_jump_tag( state );
531
632
 
532
- switch ( rc ) {
533
- case MDBX_SUCCESS:
534
- return val;
535
- case MDBX_NOTFOUND:
536
- return Qnil;
537
- default:
538
- rb_raise( rmdbx_eDatabaseError, "Unable to store value: (%d) %s", rc, mdbx_strerror(rc) );
539
- }
633
+ return self;
540
634
  }
541
635
 
542
636
 
543
- /*
544
- * call-seq:
545
- * db.statistics => (hash of stats)
546
- *
547
- * Returns a hash populated with various metadata for the opened
548
- * database.
549
- *
637
+ /* Enumerate over values for the current collection.
550
638
  */
551
639
  VALUE
552
- rmdbx_stats( VALUE self )
640
+ rmdbx_each_value_i( VALUE self )
553
641
  {
554
642
  UNWRAP_DB( self, db );
555
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
643
+ MDBX_val key, data;
556
644
 
557
- return rmdbx_gather_stats( db );
645
+ if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
646
+ VALUE rv = rb_str_new( data.iov_base, data.iov_len );
647
+ rb_yield( rmdbx_deserialize( self, rv ) );
648
+
649
+ while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
650
+ rv = rb_str_new( data.iov_base, data.iov_len );
651
+ rb_yield( rmdbx_deserialize( self, rv ) );
652
+ }
653
+ }
654
+
655
+ return self;
558
656
  }
559
657
 
560
658
 
561
- /*
562
- * call-seq:
563
- * db.collection -> (collection name, or nil if in main)
564
- * db.collection( 'collection_name' ) -> db
565
- * db.collection( nil ) -> db (main)
566
- *
567
- * Gets or sets the sub-database "collection" that read/write
568
- * operations apply to.
569
- * Passing +nil+ sets the database to the main, top-level namespace.
570
- * If a block is passed, the collection automatically reverts to the
571
- * prior collection when it exits.
572
- *
573
- * db.collection( 'collection_name' ) do
574
- * [ ... ]
575
- * end # reverts to the previous collection name
659
+ /* call-seq:
660
+ * db.each_value {|value| block } => self
576
661
  *
662
+ * Calls the block once for each value, returning self.
663
+ * A transaction must be opened prior to use.
577
664
  */
578
665
  VALUE
579
- rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
666
+ rmdbx_each_value( VALUE self )
580
667
  {
581
668
  UNWRAP_DB( self, db );
582
- VALUE subdb, block;
583
- char *prev_db = NULL;
669
+ int state;
584
670
 
585
- rb_scan_args( argc, argv, "01&", &subdb, &block );
586
- if ( argc == 0 ) {
587
- if ( db->subdb == NULL ) return Qnil;
588
- return rb_str_new_cstr( db->subdb );
589
- }
671
+ CHECK_HANDLE();
672
+ rmdbx_open_cursor( db );
673
+ RETURN_ENUMERATOR( self, 0, 0 );
590
674
 
591
- /* Provide a friendlier error message if max_collections is 0. */
592
- if ( db->settings.max_collections == 0 )
593
- rb_raise( rmdbx_eDatabaseError, "Unable to change collection: collections are not enabled." );
675
+ rb_protect( rmdbx_each_value_i, self, &state );
594
676
 
595
- /* All transactions must be closed when switching database handles. */
596
- if ( db->txn )
597
- rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
677
+ mdbx_cursor_close( db->cursor );
678
+ db->cursor = NULL;
598
679
 
599
- /* Retain the prior database collection if a block was passed.
600
- */
601
- if ( rb_block_given_p() && db->subdb != NULL ) {
602
- prev_db = (char *) malloc( strlen(db->subdb) + 1 );
603
- strcpy( prev_db, db->subdb );
604
- }
680
+ if ( state ) rb_jump_tag( state );
605
681
 
606
- db->subdb = NIL_P( subdb ) ? NULL : StringValueCStr( subdb );
607
- rmdbx_close_dbi( db );
682
+ return self;
683
+ }
608
684
 
609
- /*
610
- FIXME: Immediate transaction write to auto-create new env?
611
- Fetching from here at the moment causes an error if you
612
- haven't written anything to the new collection yet.
613
- */
614
685
 
615
- /* Revert to the previous collection after the block is done.
616
- */
617
- if ( rb_block_given_p() ) {
618
- rb_yield( self );
619
- if ( db->subdb != prev_db ) {
620
- db->subdb = prev_db;
621
- rmdbx_close_dbi( db );
686
+ /* Enumerate over key and value pairs for the current collection.
687
+ */
688
+ VALUE
689
+ rmdbx_each_pair_i( VALUE self )
690
+ {
691
+ UNWRAP_DB( self, db );
692
+ MDBX_val key, data;
693
+
694
+ if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
695
+ VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
696
+ VALUE rval = rb_str_new( data.iov_base, data.iov_len );
697
+ rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
698
+
699
+ while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
700
+ rkey = rb_str_new( key.iov_base, key.iov_len );
701
+ rval = rb_str_new( data.iov_base, data.iov_len );
702
+ rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
622
703
  }
623
- xfree( prev_db );
624
704
  }
625
705
 
626
706
  return self;
627
707
  }
628
708
 
629
709
 
710
+ /* call-seq:
711
+ * db.each_pair {|key, value| block } => self
712
+ *
713
+ * Calls the block once for each key and value, returning self.
714
+ * A transaction must be opened prior to use.
715
+ */
716
+ VALUE
717
+ rmdbx_each_pair( VALUE self )
718
+ {
719
+ UNWRAP_DB( self, db );
720
+ int state;
721
+
722
+ CHECK_HANDLE();
723
+ rmdbx_open_cursor( db );
724
+ RETURN_ENUMERATOR( self, 0, 0 );
725
+
726
+ rb_protect( rmdbx_each_pair_i, self, &state );
727
+
728
+ mdbx_cursor_close( db->cursor );
729
+ db->cursor = NULL;
730
+
731
+ if ( state ) rb_jump_tag( state );
732
+
733
+ return self;
734
+ }
735
+
736
+
630
737
  /*
631
738
  * Open an existing (or create a new) mdbx database at filesystem
632
739
  * +path+. In block form, the database is automatically closed.
@@ -666,57 +773,108 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
666
773
  db->state.open = 0;
667
774
  db->state.retain_txn = -1;
668
775
  db->settings.env_flags = MDBX_ENV_DEFAULTS;
776
+ db->settings.db_flags = MDBX_DB_DEFAULTS | MDBX_CREATE;
669
777
  db->settings.mode = 0644;
670
778
  db->settings.max_collections = 0;
671
779
  db->settings.max_readers = 0;
672
780
  db->settings.max_size = 0;
673
781
 
674
- /* Options setup, overrides.
782
+ /* Set instance variables.
675
783
  */
676
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("mode") ) );
677
- if ( ! NIL_P(opt) ) db->settings.mode = FIX2INT( opt );
678
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_collections") ) );
784
+ rb_iv_set( self, "@path", path );
785
+ rb_iv_set( self, "@options", rb_hash_dup( opts ) );
786
+
787
+ /* Environment and database options setup, overrides.
788
+ */
789
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("coalesce") ) );
790
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_COALESCE;
791
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("compatible") ) );
792
+ if ( RTEST(opt) ) {
793
+ db->settings.db_flags = db->settings.db_flags | MDBX_DB_ACCEDE;
794
+ db->settings.env_flags = db->settings.env_flags | MDBX_ACCEDE;
795
+ }
796
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("exclusive") ) );
797
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_EXCLUSIVE;
798
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("lifo_reclaim") ) );
799
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_LIFORECLAIM;
800
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("max_collections") ) );
679
801
  if ( ! NIL_P(opt) ) db->settings.max_collections = FIX2INT( opt );
680
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_readers") ) );
802
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("max_readers") ) );
681
803
  if ( ! NIL_P(opt) ) db->settings.max_readers = FIX2INT( opt );
682
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_size") ) );
804
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("max_size") ) );
683
805
  if ( ! NIL_P(opt) ) db->settings.max_size = NUM2LONG( opt );
684
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("nosubdir") ) );
806
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("mode") ) );
807
+ if ( ! NIL_P(opt) ) db->settings.mode = FIX2INT( opt );
808
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_memory_init") ) );
809
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMEMINIT;
810
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_metasync") ) );
811
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMETASYNC;
812
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_subdir") ) );
685
813
  if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOSUBDIR;
686
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("readonly") ) );
814
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_readahead") ) );
815
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NORDAHEAD;
816
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_threadlocal") ) );
817
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOTLS;
818
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("readonly") ) );
687
819
  if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_RDONLY;
688
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("exclusive") ) );
689
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_EXCLUSIVE;
690
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("compat") ) );
691
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_ACCEDE;
692
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("writemap") ) );
820
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("writemap") ) );
693
821
  if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_WRITEMAP;
694
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_threadlocal") ) );
695
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOTLS;
696
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_readahead") ) );
697
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NORDAHEAD;
698
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_memory_init") ) );
699
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMEMINIT;
700
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("coalesce") ) );
701
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_COALESCE;
702
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("lifo_reclaim") ) );
703
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_LIFORECLAIM;
704
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_metasync") ) );
705
- if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMETASYNC;
706
822
 
707
- /* Duplicate keys, on mdbx_dbi_open, maybe set here? */
708
- /* MDBX_DUPSORT = UINT32_C(0x04), */
709
-
710
- /* Set instance variables.
711
- */
712
- rb_iv_set( self, "@path", path );
713
- rb_iv_set( self, "@options", opts );
823
+ if ( rb_hash_size_num(opts) > 0 ) {
824
+ rb_raise( rb_eArgError, "Unknown option(s): %"PRIsVALUE, opts );
825
+ }
714
826
 
715
827
  rmdbx_open_env( self );
716
828
  return self;
717
829
  }
718
830
 
719
831
 
832
+ /*
833
+ * call-seq:
834
+ * db.statistics => (hash of stats)
835
+ *
836
+ * Returns a hash populated with various metadata for the opened
837
+ * database.
838
+ *
839
+ */
840
+ VALUE
841
+ rmdbx_stats( VALUE self )
842
+ {
843
+ UNWRAP_DB( self, db );
844
+ CHECK_HANDLE();
845
+
846
+ return rmdbx_gather_stats( db );
847
+ }
848
+
849
+
850
+ /*
851
+ * call-seq:
852
+ * db.clone => [copy of db]
853
+ *
854
+ * Copy the object (clone/dup). The returned copy is closed and needs
855
+ * to be reopened before use. This function likely has limited use,
856
+ * considering you can't open two handles within the same process.
857
+ */
858
+ static VALUE rmdbx_init_copy( VALUE copy, VALUE orig )
859
+ {
860
+ rmdbx_db_t *orig_db;
861
+ rmdbx_db_t *copy_db;
862
+
863
+ if ( copy == orig ) return copy;
864
+
865
+ TypedData_Get_Struct( orig, rmdbx_db_t, &rmdbx_db_data, orig_db );
866
+ TypedData_Get_Struct( copy, rmdbx_db_t, &rmdbx_db_data, copy_db );
867
+
868
+ /* Copy all fields from the original to the copy, and force-close
869
+ the copy.
870
+ */
871
+ MEMCPY( copy_db, orig_db, rmdbx_db_t, 1 );
872
+ rmdbx_close_all( copy_db );
873
+
874
+ return copy;
875
+ }
876
+
877
+
720
878
  /*
721
879
  * Initialization for the MDBX::Database class.
722
880
  */
@@ -732,25 +890,35 @@ rmdbx_init_database()
732
890
  rb_define_alloc_func( rmdbx_cDatabase, rmdbx_alloc );
733
891
 
734
892
  rb_define_protected_method( rmdbx_cDatabase, "initialize", rmdbx_database_initialize, -1 );
735
- rb_define_method( rmdbx_cDatabase, "collection", rmdbx_set_subdb, -1 );
893
+ rb_define_protected_method( rmdbx_cDatabase, "initialize_copy", rmdbx_init_copy, 1 );
894
+
895
+ rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 0 );
736
896
  rb_define_method( rmdbx_cDatabase, "close", rmdbx_close, 0 );
737
- rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
738
897
  rb_define_method( rmdbx_cDatabase, "closed?", rmdbx_closed_p, 0 );
739
- rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
740
- rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 0 );
741
- rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
742
- rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
743
- rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
898
+ rb_define_method( rmdbx_cDatabase, "drop", rmdbx_drop, 1 );
899
+ rb_define_method( rmdbx_cDatabase, "include?", rmdbx_include, 1 );
744
900
  rb_define_method( rmdbx_cDatabase, "length", rmdbx_length, 0 );
901
+ rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
745
902
  rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
746
903
  rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
747
904
 
905
+ /* Enumerables */
906
+ rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
907
+ rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
908
+ rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
909
+
748
910
  /* Manually open/close transactions from ruby. */
911
+ rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
749
912
  rb_define_protected_method( rmdbx_cDatabase, "open_transaction", rmdbx_rb_opentxn, 1 );
750
913
  rb_define_protected_method( rmdbx_cDatabase, "close_transaction", rmdbx_rb_closetxn, 1 );
751
914
 
915
+ /* Collection functions */
916
+ rb_define_protected_method( rmdbx_cDatabase, "get_subdb", rmdbx_get_subdb, 0 );
917
+ rb_define_protected_method( rmdbx_cDatabase, "set_subdb", rmdbx_set_subdb, 1 );
918
+
752
919
  rb_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
753
920
 
754
921
  rb_require( "mdbx/database" );
755
922
  }
756
923
 
924
+