mdbx 0.1.0 → 0.3.1

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: c7cc7a297e0f41d6caa23aa581bd11b1013a64a63ec6b04e9f07daae06f153d4
4
- data.tar.gz: b5a27168b461c29f88bbaf0c1fc45ec92f0fd121735ef483c76be66adef0750e
3
+ metadata.gz: 97a83bdd83e99ab3b58cf823a914c8a7b75b5b63e33e13cd00f135aa2c8988b4
4
+ data.tar.gz: aa6687110e91dfe083332fc93299c492ce5628ad41fe26cb36d9ee33531e1039
5
5
  SHA512:
6
- metadata.gz: ea27eb32cb736c4cc5d0a154d445e9c7e53966a219ab61bdca7fd4bb886538320e47f40fb9d91569b14b0d6aacf69e0dcab045c5ba5c51416231e5abde8326cd
7
- data.tar.gz: 0b5496433f6854926f90edc33aeccf9aba653fd76c6db6903ef1d5d95bbafca538957417eba1a90ee087f09faebc2df11eab0e6ceabb67846428ecc8635776c7
6
+ metadata.gz: 732ecf85ef600ae8edb433b18d67ecf26fbc1332fc673e7e01b20637dbcffff94735a8c1d79cc43c9d293cdda7b7532670f9a0c9f101234367b19aaac03149a9
7
+ data.tar.gz: 1623ff8c9d4b6e7752ef9faaf4075961119d812c7dfd78bb619fa74eaacb3e7fd1042b66b630e5638010478f61a0f3f232fa6cf2e1a37e40ef3d010a5c3257f6
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -1,6 +1,52 @@
1
1
  # Release History for MDBX
2
2
 
3
3
  ---
4
+ ## v0.3.0 [2021-04-09] Mahlon E. Smith <mahlon@martini.nu>
5
+
6
+ Enhancements:
7
+
8
+ - Alter the behavior of #clear, so it doesn't destroy collection
9
+ environments, but just empties them.
10
+
11
+ - Add #drop, which explictly -does- destroy a collection environment.
12
+
13
+ - Switching to a collection now automatically creates its environment.
14
+
15
+ - Add include? and has_key?, for presence checks without allocating
16
+ value memory or requiring deserialization.
17
+
18
+
19
+ Bugfixes:
20
+
21
+ - Run all cursor methods through rb_protect, to ensure proper
22
+ cursor cleanup in the event of an exception mid iteration.
23
+
24
+ - Fix the block form of collections to support multiple scopes.
25
+
26
+
27
+ ## v0.2.1 [2021-04-06] Mahlon E. Smith <mahlon@martini.nu>
28
+
29
+ Enhancement:
30
+
31
+ - Automatically stringify any argument to the collection() method.
32
+
33
+
34
+ ## v0.2.0 [2021-03-19] Mahlon E. Smith <mahlon@martini.nu>
35
+
36
+ Enhancement:
37
+
38
+ - Support dup/clone. This has limited use, as there can only
39
+ be one open handle per process, but implemented in the interests
40
+ of avoiding unexpected behavior.
41
+
42
+
43
+ ## v0.1.1 [2021-03-14] Mahlon E. Smith <mahlon@martini.nu>
44
+
45
+ Bugfix:
46
+
47
+ - Don't inadvertantly close an open transaction while using hash convenience methods.
48
+
49
+
4
50
  ## v0.1.0 [2021-03-14] Mahlon E. Smith <mahlon@martini.nu>
5
51
 
6
52
  Initial public release.
@@ -6,7 +6,10 @@
6
6
  */
7
7
  #define UNWRAP_DB( val, db ) \
8
8
  rmdbx_db_t *db; \
9
- TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db );
9
+ TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db )
10
+
11
+ #define CHECK_HANDLE \
12
+ if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." )
10
13
 
11
14
 
12
15
  VALUE rmdbx_cDatabase;
@@ -160,7 +163,7 @@ rmdbx_open_env( VALUE self )
160
163
  void
161
164
  rmdbx_open_cursor( rmdbx_db_t *db )
162
165
  {
163
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
166
+ CHECK_HANDLE;
164
167
  if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
165
168
 
166
169
  int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
@@ -215,11 +218,11 @@ rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
215
218
  {
216
219
  if ( ! db->txn || db->state.retain_txn > -1 ) return;
217
220
 
218
- switch ( txnflag ) {
219
- case RMDBX_TXN_COMMIT:
220
- mdbx_txn_commit( db->txn );
221
- default:
222
- mdbx_txn_abort( db->txn );
221
+ if ( txnflag == RMDBX_TXN_COMMIT ) {
222
+ mdbx_txn_commit( db->txn );
223
+ }
224
+ else {
225
+ mdbx_txn_abort( db->txn );
223
226
  }
224
227
 
225
228
  db->txn = 0;
@@ -239,6 +242,7 @@ VALUE
239
242
  rmdbx_rb_opentxn( VALUE self, VALUE mode )
240
243
  {
241
244
  UNWRAP_DB( self, db );
245
+ CHECK_HANDLE;
242
246
 
243
247
  rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
244
248
  db->state.retain_txn = RTEST(mode) ? 1 : 0;
@@ -273,13 +277,53 @@ rmdbx_rb_closetxn( VALUE self, VALUE write )
273
277
  *
274
278
  * Empty the current collection on disk. If collections are not enabled
275
279
  * or the database handle is set to the top-level (main) db - this
276
- * deletes *all records* from the database. This is not recoverable!
280
+ * deletes *all records* from the database.
277
281
  */
278
282
  VALUE
279
283
  rmdbx_clear( VALUE self )
280
284
  {
281
285
  UNWRAP_DB( self, db );
282
286
 
287
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
288
+ int rc = mdbx_drop( db->txn, db->dbi, false );
289
+
290
+ if ( rc != MDBX_SUCCESS )
291
+ rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
292
+
293
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
294
+
295
+ return Qnil;
296
+ }
297
+
298
+
299
+ /*
300
+ * call-seq:
301
+ * db.drop( collection ) -> db
302
+ *
303
+ * Destroy a collection. You must be in the top level database to call
304
+ * this method.
305
+ */
306
+ VALUE
307
+ rmdbx_drop( VALUE self, VALUE name )
308
+ {
309
+ UNWRAP_DB( self, db );
310
+
311
+ /* Provide a friendlier error message if max_collections is 0. */
312
+ if ( db->settings.max_collections == 0 )
313
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: collections are not enabled." );
314
+
315
+ /* All transactions must be closed when dropping a database. */
316
+ if ( db->txn )
317
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: transaction open" );
318
+
319
+ /* A drop can only be performed from the top-level database. */
320
+ if ( db->subdb != NULL )
321
+ rb_raise( rmdbx_eDatabaseError, "Unable to drop collection: switch to top-level db first" );
322
+
323
+ name = rb_funcall( name, rb_intern("to_s"), 0 );
324
+ db->subdb = StringValueCStr( name );
325
+
326
+ rmdbx_close_dbi( db ); /* ensure we're reopening within the new subdb */
283
327
  rmdbx_open_txn( db, MDBX_TXN_READWRITE );
284
328
  int rc = mdbx_drop( db->txn, db->dbi, true );
285
329
 
@@ -288,10 +332,11 @@ rmdbx_clear( VALUE self )
288
332
 
289
333
  rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
290
334
 
291
- /* Refresh the environment handles. */
292
- rmdbx_open_env( self );
335
+ /* Reset the current collection to the top level. */
336
+ db->subdb = NULL;
337
+ rmdbx_close_dbi( db ); /* ensure next access is not in the defunct subdb */
293
338
 
294
- return Qnil;
339
+ return self;
295
340
  }
296
341
 
297
342
 
@@ -348,21 +393,15 @@ rmdbx_deserialize( VALUE self, VALUE val )
348
393
  }
349
394
 
350
395
 
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.
396
+ /*
397
+ * Enumerate over keys for the current collection.
356
398
  */
357
399
  VALUE
358
- rmdbx_each_key( VALUE self )
400
+ rmdbx_each_key_i( VALUE self )
359
401
  {
360
402
  UNWRAP_DB( self, db );
361
403
  MDBX_val key, data;
362
404
 
363
- rmdbx_open_cursor( db );
364
- RETURN_ENUMERATOR( self, 0, 0 );
365
-
366
405
  if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
367
406
  rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
368
407
  while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
@@ -370,27 +409,45 @@ rmdbx_each_key( VALUE self )
370
409
  }
371
410
  }
372
411
 
373
- mdbx_cursor_close( db->cursor );
374
- db->cursor = NULL;
375
412
  return self;
376
413
  }
377
414
 
378
415
 
379
416
  /* call-seq:
380
- * db.each_value {|value| block } => self
417
+ * db.each_key {|key| block } => self
381
418
  *
382
- * Calls the block once for each value, returning self.
419
+ * Calls the block once for each key, returning self.
383
420
  * A transaction must be opened prior to use.
384
421
  */
385
422
  VALUE
386
- rmdbx_each_value( VALUE self )
423
+ rmdbx_each_key( VALUE self )
387
424
  {
388
425
  UNWRAP_DB( self, db );
389
- MDBX_val key, data;
426
+ int state;
390
427
 
428
+ CHECK_HANDLE;
391
429
  rmdbx_open_cursor( db );
392
430
  RETURN_ENUMERATOR( self, 0, 0 );
393
431
 
432
+ rb_protect( rmdbx_each_key_i, self, &state );
433
+
434
+ mdbx_cursor_close( db->cursor );
435
+ db->cursor = NULL;
436
+
437
+ if ( state ) rb_jump_tag( state );
438
+
439
+ return self;
440
+ }
441
+
442
+
443
+ /* Enumerate over values for the current collection.
444
+ */
445
+ VALUE
446
+ rmdbx_each_value_i( VALUE self )
447
+ {
448
+ UNWRAP_DB( self, db );
449
+ MDBX_val key, data;
450
+
394
451
  if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
395
452
  VALUE rv = rb_str_new( data.iov_base, data.iov_len );
396
453
  rb_yield( rmdbx_deserialize( self, rv ) );
@@ -401,27 +458,45 @@ rmdbx_each_value( VALUE self )
401
458
  }
402
459
  }
403
460
 
404
- mdbx_cursor_close( db->cursor );
405
- db->cursor = NULL;
406
461
  return self;
407
462
  }
408
463
 
409
464
 
410
465
  /* call-seq:
411
- * db.each_pair {|key, value| block } => self
466
+ * db.each_value {|value| block } => self
412
467
  *
413
- * Calls the block once for each key and value, returning self.
468
+ * Calls the block once for each value, returning self.
414
469
  * A transaction must be opened prior to use.
415
470
  */
416
471
  VALUE
417
- rmdbx_each_pair( VALUE self )
472
+ rmdbx_each_value( VALUE self )
418
473
  {
419
474
  UNWRAP_DB( self, db );
420
- MDBX_val key, data;
475
+ int state;
421
476
 
477
+ CHECK_HANDLE;
422
478
  rmdbx_open_cursor( db );
423
479
  RETURN_ENUMERATOR( self, 0, 0 );
424
480
 
481
+ rb_protect( rmdbx_each_value_i, self, &state );
482
+
483
+ mdbx_cursor_close( db->cursor );
484
+ db->cursor = NULL;
485
+
486
+ if ( state ) rb_jump_tag( state );
487
+
488
+ return self;
489
+ }
490
+
491
+
492
+ /* Enumerate over key and value pairs for the current collection.
493
+ */
494
+ VALUE
495
+ rmdbx_each_pair_i( VALUE self )
496
+ {
497
+ UNWRAP_DB( self, db );
498
+ MDBX_val key, data;
499
+
425
500
  if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
426
501
  VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
427
502
  VALUE rval = rb_str_new( data.iov_base, data.iov_len );
@@ -434,12 +509,38 @@ rmdbx_each_pair( VALUE self )
434
509
  }
435
510
  }
436
511
 
512
+ return self;
513
+ }
514
+
515
+
516
+ /* call-seq:
517
+ * db.each_pair {|key, value| block } => self
518
+ *
519
+ * Calls the block once for each key and value, returning self.
520
+ * A transaction must be opened prior to use.
521
+ */
522
+ VALUE
523
+ rmdbx_each_pair( VALUE self )
524
+ {
525
+ UNWRAP_DB( self, db );
526
+ int state;
527
+
528
+ CHECK_HANDLE;
529
+ rmdbx_open_cursor( db );
530
+ RETURN_ENUMERATOR( self, 0, 0 );
531
+
532
+ rb_protect( rmdbx_each_pair_i, self, &state );
533
+
437
534
  mdbx_cursor_close( db->cursor );
438
535
  db->cursor = NULL;
536
+
537
+ if ( state ) rb_jump_tag( state );
538
+
439
539
  return self;
440
540
  }
441
541
 
442
542
 
543
+
443
544
  /* call-seq:
444
545
  * db.length -> Integer
445
546
  *
@@ -451,7 +552,7 @@ rmdbx_length( VALUE self )
451
552
  UNWRAP_DB( self, db );
452
553
  MDBX_stat mstat;
453
554
 
454
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
555
+ CHECK_HANDLE;
455
556
  rmdbx_open_txn( db, MDBX_TXN_RDONLY );
456
557
 
457
558
  int rc = mdbx_dbi_stat( db->txn, db->dbi, &mstat, sizeof(mstat) );
@@ -465,6 +566,39 @@ rmdbx_length( VALUE self )
465
566
  }
466
567
 
467
568
 
569
+ /* call-seq:
570
+ * db.include?( 'key' ) => bool
571
+ *
572
+ * Returns true if the current collection contains +key+.
573
+ */
574
+ VALUE
575
+ rmdbx_include( VALUE self, VALUE key )
576
+ {
577
+ int rc;
578
+ UNWRAP_DB( self, db );
579
+
580
+ CHECK_HANDLE;
581
+ rmdbx_open_txn( db, MDBX_TXN_RDONLY );
582
+
583
+ MDBX_val ckey = rmdbx_key_for( key );
584
+ MDBX_val data;
585
+ rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
586
+ rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
587
+
588
+ switch ( rc ) {
589
+ case MDBX_SUCCESS:
590
+ return Qtrue;
591
+
592
+ case MDBX_NOTFOUND:
593
+ return Qfalse;
594
+
595
+ default:
596
+ rmdbx_close( self );
597
+ rb_raise( rmdbx_eDatabaseError, "Unable to fetch key: (%d) %s", rc, mdbx_strerror(rc) );
598
+ }
599
+ }
600
+
601
+
468
602
  /* call-seq:
469
603
  * db[ 'key' ] => value
470
604
  *
@@ -476,7 +610,7 @@ rmdbx_get_val( VALUE self, VALUE key )
476
610
  int rc;
477
611
  UNWRAP_DB( self, db );
478
612
 
479
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
613
+ CHECK_HANDLE;
480
614
  rmdbx_open_txn( db, MDBX_TXN_RDONLY );
481
615
 
482
616
  MDBX_val ckey = rmdbx_key_for( key );
@@ -503,7 +637,8 @@ rmdbx_get_val( VALUE self, VALUE key )
503
637
  /* call-seq:
504
638
  * db[ 'key' ] = value
505
639
  *
506
- * Set a single value for +key+.
640
+ * Set a single value for +key+. If the value is +nil+, the
641
+ * key is removed.
507
642
  */
508
643
  VALUE
509
644
  rmdbx_put_val( VALUE self, VALUE key, VALUE val )
@@ -511,7 +646,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
511
646
  int rc;
512
647
  UNWRAP_DB( self, db );
513
648
 
514
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
649
+ CHECK_HANDLE;
515
650
  rmdbx_open_txn( db, MDBX_TXN_READWRITE );
516
651
 
517
652
  MDBX_val ckey = rmdbx_key_for( key );
@@ -552,41 +687,32 @@ VALUE
552
687
  rmdbx_stats( VALUE self )
553
688
  {
554
689
  UNWRAP_DB( self, db );
555
- if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
690
+ CHECK_HANDLE;
556
691
 
557
692
  return rmdbx_gather_stats( db );
558
693
  }
559
694
 
560
695
 
561
696
  /*
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
576
- *
697
+ * Return the currently selected collection, or +nil+ if at the
698
+ * top-level.
577
699
  */
578
700
  VALUE
579
- rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
701
+ rmdbx_get_subdb( VALUE self )
580
702
  {
581
703
  UNWRAP_DB( self, db );
582
- VALUE subdb, block;
583
- char *prev_db = NULL;
704
+ return ( db->subdb == NULL ) ? Qnil : rb_str_new_cstr( db->subdb );
705
+ }
584
706
 
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
- }
707
+
708
+ /*
709
+ * Sets the current collection name for read/write operations.
710
+ *
711
+ */
712
+ VALUE
713
+ rmdbx_set_subdb( VALUE self, VALUE name )
714
+ {
715
+ UNWRAP_DB( self, db );
590
716
 
591
717
  /* Provide a friendlier error message if max_collections is 0. */
592
718
  if ( db->settings.max_collections == 0 )
@@ -596,32 +722,14 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
596
722
  if ( db->txn )
597
723
  rb_raise( rmdbx_eDatabaseError, "Unable to change collection: transaction open" );
598
724
 
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
- }
725
+ db->subdb = NIL_P( name ) ? NULL : StringValueCStr( name );
605
726
 
606
- db->subdb = NIL_P( subdb ) ? NULL : StringValueCStr( subdb );
727
+ /* Reset the db handle and issue a single transaction to reify
728
+ the collection.
729
+ */
607
730
  rmdbx_close_dbi( db );
608
-
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
-
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
- }
731
+ rmdbx_open_txn( db, MDBX_TXN_READWRITE );
732
+ rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
625
733
 
626
734
  return self;
627
735
  }
@@ -717,6 +825,34 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
717
825
  }
718
826
 
719
827
 
828
+ /*
829
+ * call-seq:
830
+ * db.clone => [copy of db]
831
+ *
832
+ * Copy the object (clone/dup). The returned copy is closed and needs
833
+ * to be reopened before use. This function likely has limited use,
834
+ * considering you can't open two handles within the same process.
835
+ */
836
+ static VALUE rmdbx_init_copy( VALUE copy, VALUE orig )
837
+ {
838
+ rmdbx_db_t *orig_db;
839
+ rmdbx_db_t *copy_db;
840
+
841
+ if ( copy == orig ) return copy;
842
+
843
+ TypedData_Get_Struct( orig, rmdbx_db_t, &rmdbx_db_data, orig_db );
844
+ TypedData_Get_Struct( copy, rmdbx_db_t, &rmdbx_db_data, copy_db );
845
+
846
+ /* Copy all fields from the original to the copy, and force-close
847
+ the copy.
848
+ */
849
+ MEMCPY( copy_db, orig_db, rmdbx_db_t, 1 );
850
+ rmdbx_close_all( copy_db );
851
+
852
+ return copy;
853
+ }
854
+
855
+
720
856
  /*
721
857
  * Initialization for the MDBX::Database class.
722
858
  */
@@ -732,16 +868,19 @@ rmdbx_init_database()
732
868
  rb_define_alloc_func( rmdbx_cDatabase, rmdbx_alloc );
733
869
 
734
870
  rb_define_protected_method( rmdbx_cDatabase, "initialize", rmdbx_database_initialize, -1 );
735
- rb_define_method( rmdbx_cDatabase, "collection", rmdbx_set_subdb, -1 );
871
+ rb_define_protected_method( rmdbx_cDatabase, "initialize_copy", rmdbx_init_copy, 1 );
872
+
873
+ rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 0 );
736
874
  rb_define_method( rmdbx_cDatabase, "close", rmdbx_close, 0 );
737
- rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
738
875
  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 );
876
+ rb_define_method( rmdbx_cDatabase, "drop", rmdbx_drop, 1 );
741
877
  rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
742
- rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
743
878
  rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
879
+ rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
880
+ rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
881
+ rb_define_method( rmdbx_cDatabase, "include?", rmdbx_include, 1 );
744
882
  rb_define_method( rmdbx_cDatabase, "length", rmdbx_length, 0 );
883
+ rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
745
884
  rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
746
885
  rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
747
886
 
@@ -749,6 +888,10 @@ rmdbx_init_database()
749
888
  rb_define_protected_method( rmdbx_cDatabase, "open_transaction", rmdbx_rb_opentxn, 1 );
750
889
  rb_define_protected_method( rmdbx_cDatabase, "close_transaction", rmdbx_rb_closetxn, 1 );
751
890
 
891
+ /* Collection functions */
892
+ rb_define_protected_method( rmdbx_cDatabase, "get_subdb", rmdbx_get_subdb, 0 );
893
+ rb_define_protected_method( rmdbx_cDatabase, "set_subdb", rmdbx_set_subdb, 1 );
894
+
752
895
  rb_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
753
896
 
754
897
  rb_require( "mdbx/database" );
data/ext/mdbx_ext/stats.c CHANGED
@@ -148,7 +148,7 @@ rmdbx_gather_reader_stats(
148
148
  {
149
149
  VALUE readers = rb_ary_new();
150
150
 
151
- mdbx_reader_list( db->env, reader_list_callback, (void*)readers );
151
+ mdbx_reader_list( db->env, reader_list_callback, (void*)readers );
152
152
  rb_hash_aset( stat, ID2SYM(rb_intern("readers")), readers );
153
153
 
154
154
  return;
data/lib/mdbx.rb CHANGED
@@ -10,7 +10,7 @@ require 'mdbx_ext'
10
10
  module MDBX
11
11
 
12
12
  # The version of this gem.
13
- VERSION = '0.1.0'
13
+ VERSION = '0.3.1'
14
14
 
15
15
  end # module MDBX
16
16
 
data/lib/mdbx/database.rb CHANGED
@@ -128,16 +128,43 @@ class MDBX::Database
128
128
  attr_accessor :deserializer
129
129
 
130
130
 
131
+ alias_method :size, :length
132
+ alias_method :each, :each_pair
133
+ alias_method :has_key?, :include?
134
+
135
+
136
+ ### Gets or sets the sub-database "collection" that read/write
137
+ ### operations apply to. If a block is passed, the collection
138
+ ### automatically reverts to the prior collection when it exits.
139
+ ###
140
+ ### db.collection #=> (collection name, or nil if in main)
141
+ ### db.collection( 'collection_name' ) #=> db
142
+ ###
143
+ ### db.collection( 'collection_name' ) do
144
+ ### [ ... ]
145
+ ### end # reverts to the previous collection name
146
+ ###
147
+ def collection( name=nil )
148
+ current = self.get_subdb
149
+ return current unless name
150
+
151
+ self.set_subdb( name.to_s )
152
+ yield( self ) if block_given?
153
+
154
+ return self
155
+
156
+ ensure
157
+ self.set_subdb( current ) if name && block_given?
158
+ end
159
+ alias_method :namespace, :collection
160
+
161
+
131
162
  ### Switch to the top-level collection.
132
163
  ###
133
164
  def main
134
- return self.collection( nil )
165
+ return self.set_subdb( nil )
135
166
  end
136
167
 
137
- alias_method :namespace, :collection
138
- alias_method :size, :length
139
- alias_method :each, :each_pair
140
-
141
168
 
142
169
  #
143
170
  # Transaction methods
@@ -201,8 +228,8 @@ class MDBX::Database
201
228
  ### pairs.
202
229
  ###
203
230
  def to_a
204
- self.snapshot do
205
- return self.each_pair.to_a
231
+ return self.conditional_snapshot do
232
+ self.each_pair.to_a
206
233
  end
207
234
  end
208
235
 
@@ -210,8 +237,8 @@ class MDBX::Database
210
237
  ### Return the entirety of database contents as a Hash.
211
238
  ###
212
239
  def to_h
213
- self.snapshot do
214
- return self.each_pair.to_h
240
+ return self.conditional_snapshot do
241
+ self.each_pair.to_h
215
242
  end
216
243
  end
217
244
 
@@ -261,8 +288,8 @@ class MDBX::Database
261
288
  ### Returns a new Array containing all keys in the collection.
262
289
  ###
263
290
  def keys
264
- self.snapshot do
265
- return self.each_key.to_a
291
+ return self.conditional_snapshot do
292
+ self.each_key.to_a
266
293
  end
267
294
  end
268
295
 
@@ -271,8 +298,8 @@ class MDBX::Database
271
298
  ### keys. Any given keys that are not found are ignored.
272
299
  ###
273
300
  def slice( *keys )
274
- self.snapshot do
275
- return keys.each_with_object( {} ) do |key, acc|
301
+ return self.conditional_snapshot do
302
+ keys.each_with_object( {} ) do |key, acc|
276
303
  val = self[ key ]
277
304
  acc[ key ] = val if val
278
305
  end
@@ -283,8 +310,8 @@ class MDBX::Database
283
310
  ### Returns a new Array containing all values in the collection.
284
311
  ###
285
312
  def values
286
- self.snapshot do
287
- return self.each_value.to_a
313
+ return self.conditional_snapshot do
314
+ self.each_value.to_a
288
315
  end
289
316
  end
290
317
 
@@ -292,8 +319,8 @@ class MDBX::Database
292
319
  ### Returns a new Array containing values for the given +keys+.
293
320
  ###
294
321
  def values_at( *keys )
295
- self.snapshot do
296
- return keys.each_with_object( [] ) do |key, acc|
322
+ return self.conditional_snapshot do
323
+ keys.each_with_object( [] ) do |key, acc|
297
324
  acc << self[ key ]
298
325
  end
299
326
  end
@@ -329,5 +356,23 @@ class MDBX::Database
329
356
  return stats
330
357
  end
331
358
 
359
+
360
+ #########
361
+ protected
362
+ #########
363
+
364
+ ### Yield and return the block, opening a snapshot first if
365
+ ### there isn't already a transaction in progress. Closes
366
+ ### the snapshot if this method opened it.
367
+ ###
368
+ def conditional_snapshot
369
+ in_txn = self.in_transaction?
370
+ self.snapshot unless in_txn
371
+
372
+ return yield
373
+ ensure
374
+ self.abort unless in_txn
375
+ end
376
+
332
377
  end # class MDBX::Database
333
378
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdbx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mahlon E. Smith
@@ -34,7 +34,7 @@ cert_chain:
34
34
  49pOzX5KHZLTS9DKeaP/xcGPz6C8MiwQdYrZarr2SHRASX1zFa79rkItO8kE6RDr
35
35
  b6WDF79UvZ55ajtE00TiwqjQL/ZPEtbd
36
36
  -----END CERTIFICATE-----
37
- date: 2021-03-14 00:00:00.000000000 Z
37
+ date: 2021-05-16 00:00:00.000000000 Z
38
38
  dependencies:
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: pry
metadata.gz.sig CHANGED
Binary file