mdbx 0.2.0 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,45 @@ 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.
109
119
  */
110
- VALUE
111
- rmdbx_in_transaction_p( VALUE self )
120
+ void
121
+ rmdbx_key_for( VALUE key, MDBX_val *ckey )
112
122
  {
113
- UNWRAP_DB( self, db );
114
- return db->txn ? Qtrue : Qfalse;
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
+ memcpy( ckey->iov_base, StringValuePtr(key_str), ckey->iov_len );
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
+ val = rb_funcall( self, rb_intern("serialize"), 1, val );
139
+ Check_Type( val, T_STRING );
140
+
141
+ data->iov_len = RSTRING_LEN( val );
142
+ data->iov_base = malloc( data->iov_len );
143
+ memcpy( data->iov_base, StringValuePtr(val), data->iov_len );
115
144
  }
116
145
 
117
146
 
@@ -148,28 +177,286 @@ rmdbx_open_env( VALUE self )
148
177
  rmdbx_close_all( db );
149
178
  rb_raise( rmdbx_eDatabaseError, "mdbx_env_open: (%d) %s", rc, mdbx_strerror(rc) );
150
179
  }
151
- db->state.open = 1;
152
180
 
181
+ db->state.open = 1;
153
182
  return Qtrue;
154
183
  }
155
184
 
156
185
 
157
186
  /*
158
- * Open a cursor for iteration.
187
+ * call-seq:
188
+ * db.clear
189
+ *
190
+ * Empty the current collection on disk. If collections are not enabled
191
+ * or the database handle is set to the top-level (main) db - this
192
+ * deletes *all records* from the database.
159
193
  */
160
- void
161
- rmdbx_open_cursor( rmdbx_db_t *db )
194
+ VALUE
195
+ rmdbx_clear( VALUE self )
162
196
  {
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." );
197
+ UNWRAP_DB( self, db );
165
198
 
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) );
199
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
200
+ int rc = mdbx_drop( db->txn, db->dbi, false );
201
+
202
+ if ( rc != MDBX_SUCCESS )
203
+ rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
204
+
205
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
206
+
207
+ return Qnil;
208
+ }
209
+
210
+
211
+ /*
212
+ * call-seq:
213
+ * db.drop( collection ) -> db
214
+ *
215
+ * Destroy a collection. You must be in the top level database to call
216
+ * this method.
217
+ */
218
+ VALUE
219
+ rmdbx_drop( VALUE self, VALUE name )
220
+ {
221
+ UNWRAP_DB( self, db );
222
+
223
+ /* Provide a friendlier error message if max_collections is 0. */
224
+ if ( db->settings.max_collections == 0 )
225
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: collections are not enabled." );
226
+
227
+ /* All transactions must be closed when dropping a database. */
228
+ if ( db->txn )
229
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: transaction open" );
230
+
231
+ /* A drop can only be performed from the top-level database. */
232
+ if ( db->subdb != NULL )
233
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: switch to top-level db first" );
234
+
235
+ name = rb_funcall( name, rb_intern("to_s"), 0 );
236
+ db->subdb = StringValueCStr( name );
237
+
238
+ rmdbx_close_dbi( db ); /* ensure we're reopening within the new subdb */
239
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
240
+ int rc = mdbx_drop( db->txn, db->dbi, true );
241
+
242
+ if ( rc != MDBX_SUCCESS )
243
+ rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
244
+
245
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
246
+
247
+ /* Reset the current collection to the top level. */
248
+ db->subdb = NULL;
249
+ rmdbx_close_dbi( db ); /* ensure next access is not in the defunct subdb */
250
+
251
+ return self;
252
+ }
253
+
254
+
255
+ /* call-seq:
256
+ * db.length -> Integer
257
+ *
258
+ * Returns the count of keys in the currently selected collection.
259
+ */
260
+ VALUE
261
+ rmdbx_length( VALUE self )
262
+ {
263
+ UNWRAP_DB( self, db );
264
+ MDBX_stat mstat;
265
+
266
+ CHECK_HANDLE();
267
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
268
+
269
+ int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
270
+ if ( rc != MDBX_SUCCESS )
271
+ rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_stat: (%d) %s", rc, mdbx_strerror(rc) );
272
+
273
+ VALUE rv = LONG2FIX( mstat.ms_entries );
274
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
275
+
276
+ return rv;
277
+ }
278
+
279
+
280
+ /* call-seq:
281
+ * db.include?( 'key' ) => bool
282
+ *
283
+ * Returns true if the current collection contains +key+.
284
+ */
285
+ VALUE
286
+ rmdbx_include( VALUE self, VALUE key )
287
+ {
288
+ UNWRAP_DB( self, db );
289
+
290
+ CHECK_HANDLE();
291
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
292
+
293
+ MDBX_val ckey;
294
+ MDBX_val data;
295
+ rmdbx_key_for( key, &ckey );
296
+
297
+ int rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
298
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
299
+ xfree( ckey.iov_base );
300
+
301
+ switch ( rc ) {
302
+ case MDBX_SUCCESS:
303
+ return Qtrue;
304
+
305
+ case MDBX_NOTFOUND:
306
+ return Qfalse;
307
+
308
+ default:
309
+ rmdbx_close( self );
310
+ rb_raise( rmdbx_eDatabaseError, "Unable to fetch key: (%d) %s", rc, mdbx_strerror(rc) );
170
311
  }
312
+ }
171
313
 
172
- return;
314
+
315
+ /* call-seq:
316
+ * db[ 'key' ] => value
317
+ *
318
+ * Return a single value for +key+ immediately.
319
+ */
320
+ VALUE
321
+ rmdbx_get_val( VALUE self, VALUE key )
322
+ {
323
+ UNWRAP_DB( self, db );
324
+
325
+ CHECK_HANDLE();
326
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
327
+
328
+ MDBX_val ckey;
329
+ MDBX_val data;
330
+
331
+ rmdbx_key_for( key, &ckey );
332
+ int rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
333
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
334
+ xfree( ckey.iov_base );
335
+
336
+ VALUE rv;
337
+ switch ( rc ) {
338
+ case MDBX_SUCCESS:
339
+ rv = rb_str_new( data.iov_base, data.iov_len );
340
+ return rb_funcall( self, rb_intern("deserialize"), 1, rv );
341
+
342
+ case MDBX_NOTFOUND:
343
+ return Qnil;
344
+
345
+ default:
346
+ rmdbx_close( self );
347
+ rb_raise( rmdbx_eDatabaseError, "Unable to fetch value: (%d) %s", rc, mdbx_strerror(rc) );
348
+ }
349
+ }
350
+
351
+
352
+ /* call-seq:
353
+ * db[ 'key' ] = value
354
+ *
355
+ * Set a single value for +key+. If the value is +nil+, the
356
+ * key is removed.
357
+ */
358
+ VALUE
359
+ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
360
+ {
361
+ int rc;
362
+ UNWRAP_DB( self, db );
363
+
364
+ CHECK_HANDLE();
365
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
366
+
367
+ MDBX_val ckey;
368
+ rmdbx_key_for( key, &ckey );
369
+
370
+ if ( NIL_P(val) ) { /* remove if set to nil */
371
+ rc = mdbx_del( db->txn, db->dbi, &ckey, NULL );
372
+ }
373
+ else {
374
+ MDBX_val old;
375
+ MDBX_val data;
376
+ rmdbx_val_for( self, val, &data );
377
+ rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
378
+ xfree( data.iov_base );
379
+ }
380
+
381
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
382
+ xfree( ckey.iov_base );
383
+
384
+ switch ( rc ) {
385
+ case MDBX_SUCCESS:
386
+ return val;
387
+ case MDBX_NOTFOUND:
388
+ return Qnil;
389
+ default:
390
+ rb_raise( rmdbx_eDatabaseError, "Unable to update value: (%d) %s", rc, mdbx_strerror(rc) );
391
+ }
392
+ }
393
+
394
+
395
+ /*
396
+ * Return the currently selected collection, or +nil+ if at the
397
+ * top-level.
398
+ */
399
+ VALUE
400
+ rmdbx_get_subdb( VALUE self )
401
+ {
402
+ UNWRAP_DB( self, db );
403
+ return ( db->subdb == NULL ) ? Qnil : rb_str_new_cstr( db->subdb );
404
+ }
405
+
406
+
407
+ /*
408
+ * Sets the current collection name for read/write operations.
409
+ *
410
+ */
411
+ VALUE
412
+ rmdbx_set_subdb( VALUE self, VALUE name )
413
+ {
414
+ UNWRAP_DB( self, db );
415
+
416
+ /* Provide a friendlier error message if max_collections is 0. */
417
+ if ( db->settings.max_collections == 0 )
418
+ rb_raise( rmdbx_eDatabaseError, "Unable to change collection: collections are not enabled." );
419
+
420
+ /* All transactions must be closed when switching database handles. */
421
+ if ( db->txn )
422
+ rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
423
+
424
+ xfree( db->subdb );
425
+ db->subdb = NULL;
426
+
427
+ if ( ! NIL_P(name) ) {
428
+ size_t len = RSTRING_LEN( name ) + 1;
429
+ db->subdb = malloc( len );
430
+ strlcpy( db->subdb, StringValuePtr(name), len );
431
+ rmdbx_log_obj( self, "debug", "setting subdb: %s", RSTRING_PTR(name) );
432
+ }
433
+ else {
434
+ rmdbx_log_obj( self, "debug", "clearing subdb" );
435
+ }
436
+
437
+ /* Reset the db handle and issue a single transaction to reify
438
+ the collection.
439
+ */
440
+ rmdbx_close_dbi( db );
441
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
442
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
443
+
444
+ return self;
445
+ }
446
+
447
+
448
+ /*
449
+ * call-seq:
450
+ * db.in_transaction? => false
451
+ *
452
+ * Predicate: return true if a transaction (or snapshot)
453
+ * is currently open.
454
+ */
455
+ VALUE
456
+ rmdbx_in_transaction_p( VALUE self )
457
+ {
458
+ UNWRAP_DB( self, db );
459
+ return db->txn ? Qtrue : Qfalse;
173
460
  }
174
461
 
175
462
 
@@ -191,8 +478,7 @@ rmdbx_open_txn( rmdbx_db_t *db, int rwflag )
191
478
  }
192
479
 
193
480
  if ( db->dbi == 0 ) {
194
- // FIXME: dbi_flags
195
- rc = mdbx_dbi_open( db->txn, db->subdb, MDBX_CREATE, &db->dbi );
481
+ rc = mdbx_dbi_open( db->txn, db->subdb, db->settings.db_flags, &db->dbi );
196
482
  if ( rc != MDBX_SUCCESS ) {
197
483
  rmdbx_close_all( db );
198
484
  rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
@@ -215,11 +501,11 @@ rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
215
501
  {
216
502
  if ( ! db->txn || db->state.retain_txn > -1 ) return;
217
503
 
218
- switch ( txnflag ) {
219
- case RMDBX_TXN_COMMIT:
220
- mdbx_txn_commit( db->txn );
221
- default:
222
- mdbx_txn_abort( db->txn );
504
+ if ( txnflag == RMDBX_TXN_COMMIT ) {
505
+ mdbx_txn_commit( db->txn );
506
+ }
507
+ else {
508
+ mdbx_txn_abort( db->txn );
223
509
  }
224
510
 
225
511
  db->txn = 0;
@@ -239,6 +525,7 @@ VALUE
239
525
  rmdbx_rb_opentxn( VALUE self, VALUE mode )
240
526
  {
241
527
  UNWRAP_DB( self, db );
528
+ CHECK_HANDLE();
242
529
 
243
530
  rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
244
531
  db->state.retain_txn = RTEST(mode) ? 1 : 0;
@@ -255,114 +542,46 @@ rmdbx_rb_opentxn( VALUE self, VALUE mode )
255
542
  * the transaction is committed. Otherwise, rolled back.
256
543
  *
257
544
  */
258
- VALUE
259
- rmdbx_rb_closetxn( VALUE self, VALUE write )
260
- {
261
- UNWRAP_DB( self, db );
262
-
263
- db->state.retain_txn = -1;
264
- rmdbx_close_txn( db, RTEST(write) ? RMDBX_TXN_COMMIT : RMDBX_TXN_ROLLBACK );
265
-
266
- return Qtrue;
267
- }
268
-
269
-
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 );
282
-
283
- rmdbx_open_txn( db, MDBX_TXN_READWRITE );
284
- int rc = mdbx_drop( db->txn, db->dbi, true );
285
-
286
- if ( rc != MDBX_SUCCESS )
287
- rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
288
-
289
- rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
290
-
291
- /* Refresh the environment handles. */
292
- rmdbx_open_env( self );
293
-
294
- return Qnil;
295
- }
296
-
297
-
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.
302
- */
303
- MDBX_val
304
- rmdbx_key_for( VALUE arg )
545
+ VALUE
546
+ rmdbx_rb_closetxn( VALUE self, VALUE write )
305
547
  {
306
- MDBX_val rv;
548
+ UNWRAP_DB( self, db );
307
549
 
308
- arg = rb_funcall( arg, rb_intern("to_s"), 0 );
309
- rv.iov_len = RSTRING_LEN( arg );
310
- rv.iov_base = StringValuePtr( arg );
550
+ db->state.retain_txn = -1;
551
+ rmdbx_close_txn( db, RTEST(write) ? RMDBX_TXN_COMMIT : RMDBX_TXN_ROLLBACK );
311
552
 
312
- return rv;
553
+ return Qtrue;
313
554
  }
314
555
 
315
556
 
316
557
  /*
317
- * Given a ruby +arg+, convert and return a structure
318
- * suitable for usage as a value for mdbx.
558
+ * Open a cursor for iteration.
319
559
  */
320
- MDBX_val
321
- rmdbx_val_for( VALUE self, VALUE arg )
560
+ void
561
+ rmdbx_open_cursor( rmdbx_db_t *db )
322
562
  {
323
- MDBX_val rv;
324
- VALUE serialize_proc;
325
-
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 );
563
+ CHECK_HANDLE();
564
+ if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
329
565
 
330
- rv.iov_len = RSTRING_LEN( arg );
331
- rv.iov_base = StringValuePtr( arg );
566
+ int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
567
+ if ( rc != MDBX_SUCCESS ) {
568
+ rmdbx_close_all( db );
569
+ rb_raise( rmdbx_eDatabaseError, "Unable to open cursor: (%d) %s", rc, mdbx_strerror(rc) );
570
+ }
332
571
 
333
- return rv;
572
+ return;
334
573
  }
335
574
 
336
575
 
337
576
  /*
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 );
346
-
347
- return val;
348
- }
349
-
350
-
351
- /* call-seq:
352
- * db.each_key {|key| block } => self
353
- *
354
- * Calls the block once for each key, returning self.
355
- * A transaction must be opened prior to use.
577
+ * Enumerate over keys for the current collection.
356
578
  */
357
579
  VALUE
358
- rmdbx_each_key( VALUE self )
580
+ rmdbx_each_key_i( VALUE self )
359
581
  {
360
582
  UNWRAP_DB( self, db );
361
583
  MDBX_val key, data;
362
584
 
363
- rmdbx_open_cursor( db );
364
- RETURN_ENUMERATOR( self, 0, 0 );
365
-
366
585
  if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
367
586
  rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
368
587
  while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
@@ -370,258 +589,135 @@ rmdbx_each_key( VALUE self )
370
589
  }
371
590
  }
372
591
 
373
- mdbx_cursor_close( db->cursor );
374
- db->cursor = NULL;
375
592
  return self;
376
593
  }
377
594
 
378
595
 
379
596
  /* call-seq:
380
- * db.each_value {|value| block } => self
597
+ * db.each_key {|key| block } => self
381
598
  *
382
- * Calls the block once for each value, returning self.
599
+ * Calls the block once for each key, returning self.
383
600
  * A transaction must be opened prior to use.
384
601
  */
385
602
  VALUE
386
- rmdbx_each_value( VALUE self )
603
+ rmdbx_each_key( VALUE self )
387
604
  {
388
605
  UNWRAP_DB( self, db );
389
- MDBX_val key, data;
606
+ int state;
390
607
 
608
+ CHECK_HANDLE();
391
609
  rmdbx_open_cursor( db );
392
610
  RETURN_ENUMERATOR( self, 0, 0 );
393
611
 
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 ) );
397
-
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
- }
402
- }
612
+ rb_protect( rmdbx_each_key_i, self, &state );
403
613
 
404
614
  mdbx_cursor_close( db->cursor );
405
615
  db->cursor = NULL;
616
+
617
+ if ( state ) rb_jump_tag( state );
618
+
406
619
  return self;
407
620
  }
408
621
 
409
622
 
410
- /* call-seq:
411
- * db.each_pair {|key, value| block } => self
412
- *
413
- * Calls the block once for each key and value, returning self.
414
- * A transaction must be opened prior to use.
623
+ /* Enumerate over values for the current collection.
415
624
  */
416
625
  VALUE
417
- rmdbx_each_pair( VALUE self )
626
+ rmdbx_each_value_i( VALUE self )
418
627
  {
419
628
  UNWRAP_DB( self, db );
420
629
  MDBX_val key, data;
421
630
 
422
- rmdbx_open_cursor( db );
423
- RETURN_ENUMERATOR( self, 0, 0 );
424
-
425
631
  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 ) ) );
632
+ VALUE rv = rb_str_new( data.iov_base, data.iov_len );
633
+ rb_yield( rb_funcall( self, rb_intern("deserialize"), 1, rv ) );
429
634
 
430
635
  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 ) ) );
636
+ rv = rb_str_new( data.iov_base, data.iov_len );
637
+ rb_yield( rb_funcall( self, rb_intern("deserialize"), 1, rv ) );
434
638
  }
435
639
  }
436
640
 
437
- mdbx_cursor_close( db->cursor );
438
- db->cursor = NULL;
439
641
  return self;
440
642
  }
441
643
 
442
644
 
443
645
  /* call-seq:
444
- * db.length -> Integer
445
- *
446
- * Returns the count of keys in the currently selected collection.
447
- */
448
- VALUE
449
- rmdbx_length( VALUE self )
450
- {
451
- UNWRAP_DB( self, db );
452
- MDBX_stat mstat;
453
-
454
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
455
- rmdbx_open_txn( db, MDBX_TXN_RDONLY );
456
-
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) );
460
-
461
- VALUE rv = LONG2FIX( mstat.ms_entries );
462
- rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
463
-
464
- return rv;
465
- }
466
-
467
-
468
- /* call-seq:
469
- * db[ 'key' ] => value
646
+ * db.each_value {|value| block } => self
470
647
  *
471
- * Return a single value for +key+ immediately.
648
+ * Calls the block once for each value, returning self.
649
+ * A transaction must be opened prior to use.
472
650
  */
473
651
  VALUE
474
- rmdbx_get_val( VALUE self, VALUE key )
652
+ rmdbx_each_value( VALUE self )
475
653
  {
476
- int rc;
477
654
  UNWRAP_DB( self, db );
655
+ int state;
478
656
 
479
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
480
- rmdbx_open_txn( db, MDBX_TXN_RDONLY );
657
+ CHECK_HANDLE();
658
+ rmdbx_open_cursor( db );
659
+ RETURN_ENUMERATOR( self, 0, 0 );
481
660
 
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 );
661
+ rb_protect( rmdbx_each_value_i, self, &state );
487
662
 
488
- switch ( rc ) {
489
- case MDBX_SUCCESS:
490
- rv = rb_str_new( data.iov_base, data.iov_len );
491
- return rmdbx_deserialize( self, rv );
663
+ mdbx_cursor_close( db->cursor );
664
+ db->cursor = NULL;
492
665
 
493
- case MDBX_NOTFOUND:
494
- return Qnil;
666
+ if ( state ) rb_jump_tag( state );
495
667
 
496
- default:
497
- rmdbx_close( self );
498
- rb_raise( rmdbx_eDatabaseError, "Unable to fetch value: (%d) %s", rc, mdbx_strerror(rc) );
499
- }
668
+ return self;
500
669
  }
501
670
 
502
671
 
503
- /* call-seq:
504
- * db[ 'key' ] = value
505
- *
506
- * Set a single value for +key+.
672
+ /* Enumerate over key and value pairs for the current collection.
507
673
  */
508
674
  VALUE
509
- rmdbx_put_val( VALUE self, VALUE key, VALUE val )
675
+ rmdbx_each_pair_i( VALUE self )
510
676
  {
511
- int rc;
512
677
  UNWRAP_DB( self, db );
678
+ MDBX_val key, data;
513
679
 
514
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
515
- rmdbx_open_txn( db, MDBX_TXN_READWRITE );
516
-
517
- MDBX_val ckey = rmdbx_key_for( key );
518
-
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
- }
680
+ if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
681
+ VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
682
+ VALUE rval = rb_str_new( data.iov_base, data.iov_len );
683
+ rval = rb_funcall( self, rb_intern("deserialize"), 1, rval );
684
+ rb_yield( rb_assoc_new( rkey, rval ) );
529
685
 
530
- rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
686
+ while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
687
+ rkey = rb_str_new( key.iov_base, key.iov_len );
688
+ rval = rb_str_new( data.iov_base, data.iov_len );
689
+ rval = rb_funcall( self, rb_intern("deserialize"), 1, rval );
531
690
 
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) );
691
+ rb_yield( rb_assoc_new( rkey, rval ) );
692
+ }
539
693
  }
540
- }
541
-
542
-
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
- *
550
- */
551
- VALUE
552
- rmdbx_stats( VALUE self )
553
- {
554
- UNWRAP_DB( self, db );
555
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
556
694
 
557
- return rmdbx_gather_stats( db );
695
+ return self;
558
696
  }
559
697
 
560
698
 
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
699
+ /* call-seq:
700
+ * db.each_pair {|key, value| block } => self
576
701
  *
702
+ * Calls the block once for each key and value, returning self.
703
+ * A transaction must be opened prior to use.
577
704
  */
578
705
  VALUE
579
- rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
706
+ rmdbx_each_pair( VALUE self )
580
707
  {
581
708
  UNWRAP_DB( self, db );
582
- VALUE subdb, block;
583
- char *prev_db = NULL;
709
+ int state;
584
710
 
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
- }
590
-
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." );
594
-
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" );
598
-
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
- }
711
+ CHECK_HANDLE();
712
+ rmdbx_open_cursor( db );
713
+ RETURN_ENUMERATOR( self, 0, 0 );
605
714
 
606
- db->subdb = NIL_P( subdb ) ? NULL : StringValueCStr( subdb );
607
- rmdbx_close_dbi( db );
715
+ rb_protect( rmdbx_each_pair_i, self, &state );
608
716
 
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
- */
717
+ mdbx_cursor_close( db->cursor );
718
+ db->cursor = NULL;
614
719
 
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 );
622
- }
623
- xfree( prev_db );
624
- }
720
+ if ( state ) rb_jump_tag( state );
625
721
 
626
722
  return self;
627
723
  }
@@ -666,57 +762,80 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
666
762
  db->state.open = 0;
667
763
  db->state.retain_txn = -1;
668
764
  db->settings.env_flags = MDBX_ENV_DEFAULTS;
765
+ db->settings.db_flags = MDBX_DB_DEFAULTS | MDBX_CREATE;
669
766
  db->settings.mode = 0644;
670
767
  db->settings.max_collections = 0;
671
768
  db->settings.max_readers = 0;
672
769
  db->settings.max_size = 0;
673
770
 
674
- /* Options setup, overrides.
771
+ /* Set instance variables.
675
772
  */
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") ) );
773
+ rb_iv_set( self, "@path", path );
774
+ rb_iv_set( self, "@options", rb_hash_dup( opts ) );
775
+
776
+ /* Environment and database options setup, overrides.
777
+ */
778
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("coalesce") ) );
779
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_COALESCE;
780
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("compatible") ) );
781
+ if ( RTEST(opt) ) {
782
+ db->settings.db_flags = db->settings.db_flags | MDBX_DB_ACCEDE;
783
+ db->settings.env_flags = db->settings.env_flags | MDBX_ACCEDE;
784
+ }
785
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("exclusive") ) );
786
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_EXCLUSIVE;
787
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("lifo_reclaim") ) );
788
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_LIFORECLAIM;
789
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("max_collections") ) );
679
790
  if ( ! NIL_P(opt) ) db->settings.max_collections = FIX2INT( opt );
680
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_readers") ) );
791
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("max_readers") ) );
681
792
  if ( ! NIL_P(opt) ) db->settings.max_readers = FIX2INT( opt );
682
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_size") ) );
793
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("max_size") ) );
683
794
  if ( ! NIL_P(opt) ) db->settings.max_size = NUM2LONG( opt );
684
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("nosubdir") ) );
795
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("mode") ) );
796
+ if ( ! NIL_P(opt) ) db->settings.mode = FIX2INT( opt );
797
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_memory_init") ) );
798
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMEMINIT;
799
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_metasync") ) );
800
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMETASYNC;
801
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_subdir") ) );
685
802
  if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOSUBDIR;
686
- opt = rb_hash_aref( opts, ID2SYM( rb_intern("readonly") ) );
803
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_readahead") ) );
804
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NORDAHEAD;
805
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("no_threadlocal") ) );
806
+ if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOTLS;
807
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("readonly") ) );
687
808
  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") ) );
809
+ opt = rb_hash_delete( opts, ID2SYM( rb_intern("writemap") ) );
693
810
  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
811
 
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 );
812
+ if ( rb_hash_size_num(opts) > 0 ) {
813
+ rb_raise( rb_eArgError, "Unknown option(s): %"PRIsVALUE, opts );
814
+ }
714
815
 
715
816
  rmdbx_open_env( self );
716
817
  return self;
717
818
  }
718
819
 
719
820
 
821
+ /*
822
+ * call-seq:
823
+ * db.statistics => (hash of stats)
824
+ *
825
+ * Returns a hash populated with various metadata for the opened
826
+ * database.
827
+ *
828
+ */
829
+ VALUE
830
+ rmdbx_stats( VALUE self )
831
+ {
832
+ UNWRAP_DB( self, db );
833
+ CHECK_HANDLE();
834
+
835
+ return rmdbx_gather_stats( db );
836
+ }
837
+
838
+
720
839
  /*
721
840
  * call-seq:
722
841
  * db.clone => [copy of db]
@@ -761,25 +880,34 @@ rmdbx_init_database()
761
880
 
762
881
  rb_define_protected_method( rmdbx_cDatabase, "initialize", rmdbx_database_initialize, -1 );
763
882
  rb_define_protected_method( rmdbx_cDatabase, "initialize_copy", rmdbx_init_copy, 1 );
764
- rb_define_method( rmdbx_cDatabase, "collection", rmdbx_set_subdb, -1 );
883
+
884
+ rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 0 );
765
885
  rb_define_method( rmdbx_cDatabase, "close", rmdbx_close, 0 );
766
- rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
767
886
  rb_define_method( rmdbx_cDatabase, "closed?", rmdbx_closed_p, 0 );
768
- rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
769
- rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 0 );
770
- rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
771
- rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
772
- rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
887
+ rb_define_method( rmdbx_cDatabase, "drop", rmdbx_drop, 1 );
888
+ rb_define_method( rmdbx_cDatabase, "include?", rmdbx_include, 1 );
773
889
  rb_define_method( rmdbx_cDatabase, "length", rmdbx_length, 0 );
890
+ rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
774
891
  rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
775
892
  rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
776
893
 
894
+ /* Enumerables */
895
+ rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
896
+ rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
897
+ rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
898
+
777
899
  /* Manually open/close transactions from ruby. */
900
+ rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
778
901
  rb_define_protected_method( rmdbx_cDatabase, "open_transaction", rmdbx_rb_opentxn, 1 );
779
902
  rb_define_protected_method( rmdbx_cDatabase, "close_transaction", rmdbx_rb_closetxn, 1 );
780
903
 
904
+ /* Collection functions */
905
+ rb_define_protected_method( rmdbx_cDatabase, "get_subdb", rmdbx_get_subdb, 0 );
906
+ rb_define_protected_method( rmdbx_cDatabase, "set_subdb", rmdbx_set_subdb, 1 );
907
+
781
908
  rb_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
782
909
 
783
910
  rb_require( "mdbx/database" );
784
911
  }
785
912
 
913
+