mdbx 0.1.0 → 0.3.1

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