mdbx 0.2.0 → 0.3.3

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.
@@ -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
+