mdbx 0.1.0.pre.20201217111933 → 0.1.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/History.md +5 -1
- data/README.md +305 -35
- data/ext/mdbx_ext/database.c +383 -138
- data/ext/mdbx_ext/mdbx_ext.c +8 -2
- data/ext/mdbx_ext/mdbx_ext.h +41 -3
- data/ext/mdbx_ext/stats.c +191 -0
- data/lib/mdbx.rb +1 -5
- data/lib/mdbx/database.rb +274 -9
- metadata +44 -29
- metadata.gz.sig +0 -0
- data/lib/mdbx_ext.so +0 -0
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c7cc7a297e0f41d6caa23aa581bd11b1013a64a63ec6b04e9f07daae06f153d4
         | 
| 4 | 
            +
              data.tar.gz: b5a27168b461c29f88bbaf0c1fc45ec92f0fd121735ef483c76be66adef0750e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ea27eb32cb736c4cc5d0a154d445e9c7e53966a219ab61bdca7fd4bb886538320e47f40fb9d91569b14b0d6aacf69e0dcab045c5ba5c51416231e5abde8326cd
         | 
| 7 | 
            +
              data.tar.gz: 0b5496433f6854926f90edc33aeccf9aba653fd76c6db6903ef1d5d95bbafca538957417eba1a90ee087f09faebc2df11eab0e6ceabb67846428ecc8635776c7
         | 
    
        checksums.yaml.gz.sig
    CHANGED
    
    | Binary file | 
    
        data.tar.gz.sig
    CHANGED
    
    | Binary file | 
    
        data/History.md
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
             | 
| 2 | 
            +
            # Ruby::MDBX
         | 
| 2 3 |  | 
| 3 4 | 
             
            home
         | 
| 4 5 | 
             
            : https://code.martini.nu/ruby-mdbx
         | 
| @@ -24,64 +25,333 @@ sourcehut: | |
| 24 25 | 
             
            This is a Ruby (MRI) binding for the libmdbx database library.
         | 
| 25 26 |  | 
| 26 27 | 
             
            libmdbx is an extremely fast, compact, powerful, embedded, transactional
         | 
| 27 | 
            -
            key-value database, with permissive license. libmdbx has a specific set
         | 
| 28 | 
            +
            key-value database, with a permissive license. libmdbx has a specific set
         | 
| 28 29 | 
             
            of properties and capabilities, focused on creating unique lightweight
         | 
| 29 30 | 
             
            solutions.
         | 
| 30 31 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 32 | 
            +
            For more information about libmdbx (features, limitations, etc), see the
         | 
| 33 | 
            +
            [introduction](https://erthink.github.io/libmdbx/intro.html).
         | 
| 33 34 |  | 
| 34 | 
            -
              - Provides extraordinary performance, minimal overhead through
         | 
| 35 | 
            -
              Memory-Mapping and Olog(N) operations costs by virtue of B+ tree.
         | 
| 36 35 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
              WAL, but that might be a caveat for write-intensive workloads with
         | 
| 39 | 
            -
              durability requirements.
         | 
| 36 | 
            +
            ## Prerequisites
         | 
| 40 37 |  | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
              process(es), but implements a simplified variant of the Berkeley DB
         | 
| 44 | 
            -
              and dbm API.
         | 
| 38 | 
            +
            * Ruby 2.6+
         | 
| 39 | 
            +
            * [libmdbx](https://github.com/erthink/libmdbx)
         | 
| 45 40 |  | 
| 46 | 
            -
              - Enforces serializability for writers just by single mutex and
         | 
| 47 | 
            -
              affords wait-free for parallel readers without atomic/interlocked
         | 
| 48 | 
            -
              operations, while writing and reading transactions do not block each
         | 
| 49 | 
            -
              other.
         | 
| 50 41 |  | 
| 51 | 
            -
             | 
| 52 | 
            -
              neglected in favour of write performance.
         | 
| 42 | 
            +
            ## Installation
         | 
| 53 43 |  | 
| 54 | 
            -
             | 
| 55 | 
            -
              Solaris, OpenSolaris, OpenIndiana, NetBSD, OpenBSD and other systems
         | 
| 56 | 
            -
              compliant with POSIX.1-2008.
         | 
| 44 | 
            +
                $ gem install mdbx
         | 
| 57 45 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
              all benefits from LMDB, but resolves some issues and adds a set of
         | 
| 61 | 
            -
              improvements.
         | 
| 46 | 
            +
            You may need to be specific if the libmdbx headers are located in a
         | 
| 47 | 
            +
            nonstandard location for your operating system:
         | 
| 62 48 |  | 
| 49 | 
            +
            	$ gem install mdbx -- --with-opt-dir=/usr/local
         | 
| 63 50 |  | 
| 64 | 
            -
            ### Examples
         | 
| 65 51 |  | 
| 66 | 
            -
             | 
| 52 | 
            +
            ## Usage
         | 
| 67 53 |  | 
| 54 | 
            +
            Some quick concepts:
         | 
| 68 55 |  | 
| 69 | 
            -
             | 
| 56 | 
            +
              - A **database** is contained in a file, normally contained in directory
         | 
| 57 | 
            +
                with it's associated lockfile.
         | 
| 58 | 
            +
              - Each database can optionally contain multiple named **collections**,
         | 
| 59 | 
            +
                which can be thought of as distinct namespaces.
         | 
| 60 | 
            +
              - Each collection can contain any number of **keys**, and their associated
         | 
| 61 | 
            +
                **values**. 
         | 
| 62 | 
            +
              - A **snapshot** is a self-consistent read-only view of the database.
         | 
| 63 | 
            +
                It remains consistent even if another thread or process writes changes.
         | 
| 64 | 
            +
              - A **transaction** is a writable snapshot.  Changes made within a
         | 
| 65 | 
            +
                transaction are not seen by other snapshots until committed. 
         | 
| 70 66 |  | 
| 71 | 
            -
             | 
| 72 | 
            -
            * libmdbx (https://github.com/erthink/libmdbx)
         | 
| 67 | 
            +
            ### Open (and close) a database handle
         | 
| 73 68 |  | 
| 69 | 
            +
            Open a database handle, creating an empty one if not already present.
         | 
| 74 70 |  | 
| 75 | 
            -
             | 
| 71 | 
            +
            ```ruby
         | 
| 72 | 
            +
            db = MDBX::Database.open( "/path/to/file", options )
         | 
| 73 | 
            +
            db.close
         | 
| 74 | 
            +
            ```
         | 
| 76 75 |  | 
| 77 | 
            -
             | 
| 76 | 
            +
            In block form, the handle is automatically closed.
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            ```ruby
         | 
| 79 | 
            +
            MDBX::Database.open( 'database' ) do |db|
         | 
| 80 | 
            +
                puts db[ 'key1' ]
         | 
| 81 | 
            +
            end # closed database
         | 
| 82 | 
            +
            ```
         | 
| 83 | 
            +
             | 
| 84 | 
            +
             | 
| 85 | 
            +
            ### Read data
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            You can use the database handle as a hash. Reading a value automatically
         | 
| 88 | 
            +
            creates a snapshot, retrieves the value, and closes the snapshot before
         | 
| 89 | 
            +
            returning it.
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            ```ruby
         | 
| 92 | 
            +
            db[ 'key1' ] #=> val
         | 
| 93 | 
            +
            ```
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            All data reads require a snapshot (or transaction).
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            The `snapshot` method creates a long-running snapshot manually.  In
         | 
| 98 | 
            +
            block form, the snapshot is automatically closed when the block exits.
         | 
| 99 | 
            +
            Sharing a snapshot between reads is significantly faster when fetching
         | 
| 100 | 
            +
            many values or in tight loops.
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            ```ruby
         | 
| 103 | 
            +
            # read-only block
         | 
| 104 | 
            +
            db.snapshot do
         | 
| 105 | 
            +
                db[ 'key1' ] #=> val
         | 
| 106 | 
            +
                ...
         | 
| 107 | 
            +
            end # snapshot closed
         | 
| 108 | 
            +
            ```
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            You can also open and close a snapshot manually.
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            ```ruby
         | 
| 113 | 
            +
            db.snapshot
         | 
| 114 | 
            +
            db.values_at( 'key1', 'key2' ) #=> [ value, value ]
         | 
| 115 | 
            +
            db.rollback
         | 
| 116 | 
            +
            ```
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            Technically, `snapshot` just sets the internal state and returns the
         | 
| 119 | 
            +
            database handle - the handle is also yielded when using blocks.  The
         | 
| 120 | 
            +
            following 3 examples are identical, use whatever form you prefer.
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            ```ruby
         | 
| 123 | 
            +
            snap = db.snapshot
         | 
| 124 | 
            +
            snap[ 'key1' ]
         | 
| 125 | 
            +
            snap.abort
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            db.snapshot do |snap|
         | 
| 128 | 
            +
                snap[ 'key1' ]
         | 
| 129 | 
            +
            end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            db.snapshot do
         | 
| 132 | 
            +
                db[ 'key1' ]
         | 
| 133 | 
            +
            end
         | 
| 134 | 
            +
            ```
         | 
| 135 | 
            +
             | 
| 136 | 
            +
            Attempting writes while within an open snapshot is an exception.
         | 
| 137 | 
            +
             | 
| 138 | 
            +
             | 
| 139 | 
            +
            ### Write data
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            Writing data is also hash-like.  Assigning a value to a key
         | 
| 142 | 
            +
            automatically opens a writable transaction, stores the value, and
         | 
| 143 | 
            +
            commits the transaction before returning.
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            All keys are strings, or converted to a string automatically.
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            ```ruby
         | 
| 148 | 
            +
            db[ 'key1' ] = val
         | 
| 149 | 
            +
            db[ :key1 ] == db[ 'key1' ] #=> true
         | 
| 150 | 
            +
            ```
         | 
| 151 | 
            +
             | 
| 152 | 
            +
            All data writes require a transaction.
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            The `transaction` method creates a long-running transaction manually.  In
         | 
| 155 | 
            +
            block form, the transaction is automatically closed when the block exits.
         | 
| 156 | 
            +
            Sharing a transaction between writes is significantly faster when
         | 
| 157 | 
            +
            storing many values or in tight loops.
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            ```ruby
         | 
| 160 | 
            +
            # read/write block
         | 
| 161 | 
            +
            db.transaction do
         | 
| 162 | 
            +
                db[ 'key1' ] = val
         | 
| 163 | 
            +
            end # transaction committed and closed
         | 
| 164 | 
            +
            ```
         | 
| 165 | 
            +
             | 
| 166 | 
            +
            You can also open and close a transaction manually.
         | 
| 167 | 
            +
             | 
| 168 | 
            +
            ```ruby
         | 
| 169 | 
            +
            db.transaction
         | 
| 170 | 
            +
            db[ 'key1' ] = val
         | 
| 171 | 
            +
            db.commit
         | 
| 172 | 
            +
            ```
         | 
| 173 | 
            +
             | 
| 174 | 
            +
            Like snapshots, `transaction` just sets the internal state and returns
         | 
| 175 | 
            +
            the database handle - the handle is also yielded when using blocks.  The
         | 
| 176 | 
            +
            following 3 examples are identical, use whatever form you prefer.
         | 
| 177 | 
            +
             | 
| 178 | 
            +
            ```ruby
         | 
| 179 | 
            +
            txn = db.transaction
         | 
| 180 | 
            +
            txn[ 'key1' ] = true
         | 
| 181 | 
            +
            txn.save
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            db.transaction do |txn|
         | 
| 184 | 
            +
                txn[ 'key1' ] = true
         | 
| 185 | 
            +
            end
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            db.transaction do
         | 
| 188 | 
            +
                db[ 'key1' ] = true
         | 
| 189 | 
            +
            end
         | 
| 190 | 
            +
            ```
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            ### Delete data
         | 
| 193 | 
            +
             | 
| 194 | 
            +
            Just write a `nil` value to remove a key entirely, or like Hash, use the
         | 
| 195 | 
            +
            `#delete` method:
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            ```ruby
         | 
| 198 | 
            +
            db[ 'key1' ] = nil
         | 
| 199 | 
            +
            ```
         | 
| 200 | 
            +
             | 
| 201 | 
            +
            ```ruby
         | 
| 202 | 
            +
            oldval = db.delete( 'key1' )
         | 
| 203 | 
            +
            ```
         | 
| 204 | 
            +
             | 
| 205 | 
            +
             | 
| 206 | 
            +
            ### Transactions
         | 
| 207 | 
            +
             | 
| 208 | 
            +
            Transactions are largely modelled after the
         | 
| 209 | 
            +
            [Sequel](https://sequel.jeremyevans.net/rdoc/files/doc/transactions_rdoc.html)
         | 
| 210 | 
            +
            transaction basics.
         | 
| 211 | 
            +
             | 
| 212 | 
            +
            While in a transaction block, if no exception is raised, the
         | 
| 213 | 
            +
            transaction is automatically committed and closed when the block exits.
         | 
| 214 | 
            +
             | 
| 215 | 
            +
            ```ruby
         | 
| 216 | 
            +
            db[ 'key' ] = false
         | 
| 217 | 
            +
             | 
| 218 | 
            +
            db.transaction do # BEGIN
         | 
| 219 | 
            +
                db[ 'key' ] = true
         | 
| 220 | 
            +
            end # COMMIT
         | 
| 221 | 
            +
             | 
| 222 | 
            +
            db[ 'key' ] #=> true
         | 
| 223 | 
            +
            ```
         | 
| 224 | 
            +
             | 
| 225 | 
            +
            If the block raises a MDBX::Rollback exception, the transaction is
         | 
| 226 | 
            +
            rolled back, but no exception is raised outside the block:
         | 
| 227 | 
            +
             | 
| 228 | 
            +
            ```ruby
         | 
| 229 | 
            +
            db[ 'key' ] = false
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            db.transaction do # BEGIN
         | 
| 232 | 
            +
                db[ 'key' ] = true
         | 
| 233 | 
            +
                raise MDBX::Rollback
         | 
| 234 | 
            +
            end # ROLLBACK
         | 
| 235 | 
            +
             | 
| 236 | 
            +
            db[ 'key' ] #=> false
         | 
| 237 | 
            +
            ```
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            If any other exception is raised, the transaction is rolled back, and
         | 
| 240 | 
            +
            the exception is raised outside the block:
         | 
| 241 | 
            +
             | 
| 242 | 
            +
            ```ruby
         | 
| 243 | 
            +
            db[ 'key' ] = false
         | 
| 244 | 
            +
             | 
| 245 | 
            +
            db.transaction do # BEGIN
         | 
| 246 | 
            +
                db[ 'key' ] = true
         | 
| 247 | 
            +
                raise ArgumentError
         | 
| 248 | 
            +
            end # ROLLBACK
         | 
| 249 | 
            +
             | 
| 250 | 
            +
            # ArgumentError raised
         | 
| 251 | 
            +
            ```
         | 
| 252 | 
            +
             | 
| 253 | 
            +
             | 
| 254 | 
            +
            If you want to check whether you are currently in a transaction, use the
         | 
| 255 | 
            +
            Database#in_transaction? method:
         | 
| 256 | 
            +
             | 
| 257 | 
            +
            ```ruby
         | 
| 258 | 
            +
            db.in_transaction? #=> false
         | 
| 259 | 
            +
            db.transaction do
         | 
| 260 | 
            +
                db.in_transaction? #=> true
         | 
| 261 | 
            +
            end
         | 
| 262 | 
            +
            ```
         | 
| 263 | 
            +
             | 
| 264 | 
            +
            MDBX writes are strongly serialized, and an open transaction blocks
         | 
| 265 | 
            +
            other writers until it has completed.  Snapshots have no such
         | 
| 266 | 
            +
            serialization, and readers from separate processes do not interfere with
         | 
| 267 | 
            +
            each other.  Be aware of libmdbx behaviors while in open transactions.
         | 
| 268 | 
            +
             | 
| 269 | 
            +
             | 
| 270 | 
            +
            ### Collections
         | 
| 271 | 
            +
             | 
| 272 | 
            +
            A MDBX collection is a sub-database, or a namespace.  In order to use
         | 
| 273 | 
            +
            this feature, the database must be opened with the `max_collections`
         | 
| 274 | 
            +
            option:
         | 
| 275 | 
            +
             | 
| 276 | 
            +
            ```ruby
         | 
| 277 | 
            +
            db = MDBX::Database.open( "/path/to/file", max_collections: 10 )
         | 
| 278 | 
            +
            ```
         | 
| 279 | 
            +
             | 
| 280 | 
            +
            Afterwards, you can switch collections at will.
         | 
| 281 | 
            +
             | 
| 282 | 
            +
            ```ruby
         | 
| 283 | 
            +
            db.collection( 'sub' )
         | 
| 284 | 
            +
            db.collection #=> 'sub'
         | 
| 285 | 
            +
            db[ :key ] = true
         | 
| 286 | 
            +
            db.main # switch to the top level
         | 
| 287 | 
            +
            db[ :key ] #=> nil
         | 
| 288 | 
            +
            ```
         | 
| 289 | 
            +
             | 
| 290 | 
            +
            In block form, the collection is reverted to the current collection when
         | 
| 291 | 
            +
            the block was started:
         | 
| 292 | 
            +
             | 
| 293 | 
            +
            ```ruby
         | 
| 294 | 
            +
            db.collection( 'sub1' )
         | 
| 295 | 
            +
            db.collection( 'sub2' ) do
         | 
| 296 | 
            +
                db[ :key ] = true
         | 
| 297 | 
            +
            end # the collection is reverted to 'sub1'
         | 
| 298 | 
            +
            ```
         | 
| 299 | 
            +
             | 
| 300 | 
            +
            Collections cannot be switched while a snapshot or transaction is open.
         | 
| 301 | 
            +
             | 
| 302 | 
            +
            Collection names are stored in the top-level database as keys.  Attempts
         | 
| 303 | 
            +
            to use these keys as regular values, or switching to a key that is not
         | 
| 304 | 
            +
            a collection will result in an incompatibility error.  While using
         | 
| 305 | 
            +
            collections, It's probably wise to not store regular key/value data in a
         | 
| 306 | 
            +
            top-level database to avoid this ambiguity.
         | 
| 307 | 
            +
             | 
| 308 | 
            +
             | 
| 309 | 
            +
            ### Value Serialization
         | 
| 310 | 
            +
             | 
| 311 | 
            +
            By default, all values are stored as Marshal data - this is the most
         | 
| 312 | 
            +
            "Ruby" behavior, as you can store any Ruby object directly that supports
         | 
| 313 | 
            +
            `Marshal.dump`.
         | 
| 314 | 
            +
             | 
| 315 | 
            +
            ```ruby
         | 
| 316 | 
            +
            db.serializer   = ->( v ) { Marshal.dump( v ) }
         | 
| 317 | 
            +
            db.deserializer = ->( v ) { Marshal.load( v ) }
         | 
| 318 | 
            +
            ```
         | 
| 319 | 
            +
             | 
| 320 | 
            +
            For compatibility with databases used by other languages, or if your
         | 
| 321 | 
            +
            needs are more specific, you can disable or override the default
         | 
| 322 | 
            +
            serialization behaviors after opening the database.
         | 
| 323 | 
            +
             | 
| 324 | 
            +
            ```ruby
         | 
| 325 | 
            +
            # All values are JSON strings
         | 
| 326 | 
            +
            db.serializer   = ->( v ) { JSON.generate( v ) }
         | 
| 327 | 
            +
            db.deserializer = ->( v ) { JSON.parse( v ) }
         | 
| 328 | 
            +
            ```
         | 
| 329 | 
            +
             | 
| 330 | 
            +
            ```ruby
         | 
| 331 | 
            +
            # Disable all automatic serialization
         | 
| 332 | 
            +
            db.serializer   = nil
         | 
| 333 | 
            +
            db.deserializer = nil
         | 
| 334 | 
            +
            ```
         | 
| 335 | 
            +
             | 
| 336 | 
            +
            ### Introspection
         | 
| 337 | 
            +
             | 
| 338 | 
            +
            Calling `statistics` on a database handle will provide a subset of
         | 
| 339 | 
            +
            information about the build environment, the database environment, and
         | 
| 340 | 
            +
            the currently connected clients.
         | 
| 341 | 
            +
             | 
| 342 | 
            +
             | 
| 343 | 
            +
            ## TODO
         | 
| 344 | 
            +
             | 
| 345 | 
            +
              - Expose more database/collection information to statistics
         | 
| 346 | 
            +
              - Support libmdbx multiple values per key DUPSORT via `put`, `get`
         | 
| 347 | 
            +
                Enumerators, and a 'value' argument for `delete`.
         | 
| 78 348 |  | 
| 79 349 |  | 
| 80 350 | 
             
            ## Contributing
         | 
| 81 351 |  | 
| 82 352 | 
             
            You can check out the current development source with Mercurial via its
         | 
| 83 353 | 
             
            [home repo](https://code.martini.nu/ruby-mdbx), or with Git at its
         | 
| 84 | 
            -
            [project  | 
| 354 | 
            +
            [project mirror](https://gitlab.com/mahlon/ruby-mdbx).
         | 
| 85 355 |  | 
| 86 356 | 
             
            After checking out the source, run:
         | 
| 87 357 |  | 
| @@ -99,7 +369,7 @@ development. | |
| 99 369 |  | 
| 100 370 | 
             
            ## License
         | 
| 101 371 |  | 
| 102 | 
            -
            Copyright (c) 2020 | 
| 372 | 
            +
            Copyright (c) 2020-2021 Mahlon E. Smith
         | 
| 103 373 | 
             
            All rights reserved.
         | 
| 104 374 |  | 
| 105 375 | 
             
            Redistribution and use in source and binary forms, with or without
         | 
    
        data/ext/mdbx_ext/database.c
    CHANGED
    
    | @@ -2,42 +2,18 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            #include "mdbx_ext.h"
         | 
| 4 4 |  | 
| 5 | 
            -
            /* VALUE str = rb_sprintf( "path: %+"PRIsVALUE", opts: %+"PRIsVALUE, path, opts ); */
         | 
| 6 | 
            -
            /* printf( "%s\n", StringValueCStr(str) ); */
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            VALUE rmdbx_cDatabase;
         | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 5 | 
             
            /* Shortcut for fetching current DB variables.
         | 
| 12 | 
            -
             *
         | 
| 13 6 | 
             
             */
         | 
| 14 7 | 
             
            #define UNWRAP_DB( val, db ) \
         | 
| 15 8 | 
             
            	rmdbx_db_t *db; \
         | 
| 16 9 | 
             
            	TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db );
         | 
| 17 10 |  | 
| 18 11 |  | 
| 19 | 
            -
             | 
| 20 | 
            -
             * A struct encapsulating an instance's DB state.
         | 
| 21 | 
            -
             */
         | 
| 22 | 
            -
            struct rmdbx_db {
         | 
| 23 | 
            -
            	MDBX_env *env;
         | 
| 24 | 
            -
            	MDBX_dbi dbi;
         | 
| 25 | 
            -
            	MDBX_txn *txn;
         | 
| 26 | 
            -
            	MDBX_cursor *cursor;
         | 
| 27 | 
            -
            	int env_flags;
         | 
| 28 | 
            -
            	int mode;
         | 
| 29 | 
            -
            	int open;
         | 
| 30 | 
            -
            	int max_collections;
         | 
| 31 | 
            -
            	char *path;
         | 
| 32 | 
            -
            	char *subdb;
         | 
| 33 | 
            -
            };
         | 
| 34 | 
            -
            typedef struct rmdbx_db rmdbx_db_t;
         | 
| 35 | 
            -
             | 
| 12 | 
            +
            VALUE rmdbx_cDatabase;
         | 
| 36 13 |  | 
| 37 14 | 
             
            /*
         | 
| 38 15 | 
             
             * Ruby allocation hook.
         | 
| 39 16 | 
             
             */
         | 
| 40 | 
            -
            void rmdbx_free( void *db );  /* forward declaration */
         | 
| 41 17 | 
             
            static const rb_data_type_t rmdbx_db_data = {
         | 
| 42 18 | 
             
            	.wrap_struct_name = "MDBX::Database::Data",
         | 
| 43 19 | 
             
            	.function = { .dfree = rmdbx_free },
         | 
| @@ -61,13 +37,27 @@ rmdbx_alloc( VALUE klass ) | |
| 61 37 | 
             
             * removed.
         | 
| 62 38 | 
             
             */
         | 
| 63 39 | 
             
            void
         | 
| 64 | 
            -
            rmdbx_close_all( rmdbx_db_t* | 
| 40 | 
            +
            rmdbx_close_all( rmdbx_db_t *db )
         | 
| 65 41 | 
             
            {
         | 
| 66 42 | 
             
            	if ( db->cursor ) mdbx_cursor_close( db->cursor );
         | 
| 67 43 | 
             
            	if ( db->txn )    mdbx_txn_abort( db->txn );
         | 
| 68 44 | 
             
            	if ( db->dbi )    mdbx_dbi_close( db->env, db->dbi );
         | 
| 69 45 | 
             
            	if ( db->env )    mdbx_env_close( db->env );
         | 
| 70 | 
            -
            	db->open = 0;
         | 
| 46 | 
            +
            	db->state.open = 0;
         | 
| 47 | 
            +
            }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
             | 
| 50 | 
            +
            /*
         | 
| 51 | 
            +
             * Close any open database handle.  Will be automatically
         | 
| 52 | 
            +
             * re-opened on next transaction.  This is primarily useful for
         | 
| 53 | 
            +
             * switching between subdatabases.
         | 
| 54 | 
            +
             */
         | 
| 55 | 
            +
            void
         | 
| 56 | 
            +
            rmdbx_close_dbi( rmdbx_db_t *db )
         | 
| 57 | 
            +
            {
         | 
| 58 | 
            +
            	if ( ! db->dbi ) return;
         | 
| 59 | 
            +
            	mdbx_dbi_close( db->env, db->dbi );
         | 
| 60 | 
            +
            	db->dbi = 0;
         | 
| 71 61 | 
             
            }
         | 
| 72 62 |  | 
| 73 63 |  | 
| @@ -79,13 +69,13 @@ rmdbx_free( void *db ) | |
| 79 69 | 
             
            {
         | 
| 80 70 | 
             
            	if ( db ) {
         | 
| 81 71 | 
             
            		rmdbx_close_all( db );
         | 
| 82 | 
            -
            		 | 
| 72 | 
            +
            		xfree( db );
         | 
| 83 73 | 
             
            	}
         | 
| 84 74 | 
             
            }
         | 
| 85 75 |  | 
| 86 76 |  | 
| 87 77 | 
             
            /*
         | 
| 88 | 
            -
             * Cleanly close an opened database | 
| 78 | 
            +
             * Cleanly close an opened database.
         | 
| 89 79 | 
             
             */
         | 
| 90 80 | 
             
            VALUE
         | 
| 91 81 | 
             
            rmdbx_close( VALUE self )
         | 
| @@ -96,8 +86,38 @@ rmdbx_close( VALUE self ) | |
| 96 86 | 
             
            }
         | 
| 97 87 |  | 
| 98 88 |  | 
| 89 | 
            +
            /*
         | 
| 90 | 
            +
             * call-seq:
         | 
| 91 | 
            +
             *    db.closed? => false
         | 
| 92 | 
            +
             *
         | 
| 93 | 
            +
             * Predicate: return true if the database environment is closed.
         | 
| 94 | 
            +
             */
         | 
| 95 | 
            +
            VALUE
         | 
| 96 | 
            +
            rmdbx_closed_p( VALUE self )
         | 
| 97 | 
            +
            {
         | 
| 98 | 
            +
            	UNWRAP_DB( self, db );
         | 
| 99 | 
            +
            	return db->state.open == 1 ? Qfalse : Qtrue;
         | 
| 100 | 
            +
            }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
             | 
| 103 | 
            +
            /*
         | 
| 104 | 
            +
             * call-seq:
         | 
| 105 | 
            +
             *    db.in_transaction? => false
         | 
| 106 | 
            +
             *
         | 
| 107 | 
            +
             * Predicate: return true if a transaction (or snapshot)
         | 
| 108 | 
            +
             * is currently open.
         | 
| 109 | 
            +
             */
         | 
| 110 | 
            +
            VALUE
         | 
| 111 | 
            +
            rmdbx_in_transaction_p( VALUE self )
         | 
| 112 | 
            +
            {
         | 
| 113 | 
            +
            	UNWRAP_DB( self, db );
         | 
| 114 | 
            +
            	return db->txn ? Qtrue : Qfalse;
         | 
| 115 | 
            +
            }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
             | 
| 99 118 | 
             
            /*
         | 
| 100 119 | 
             
             * Open the DB environment handle.
         | 
| 120 | 
            +
             *
         | 
| 101 121 | 
             
             */
         | 
| 102 122 | 
             
            VALUE
         | 
| 103 123 | 
             
            rmdbx_open_env( VALUE self )
         | 
| @@ -113,48 +133,60 @@ rmdbx_open_env( VALUE self ) | |
| 113 133 | 
             
            		rb_raise( rmdbx_eDatabaseError, "mdbx_env_create: (%d) %s", rc, mdbx_strerror(rc) );
         | 
| 114 134 |  | 
| 115 135 | 
             
            	/* Set the maximum number of named databases for the environment. */
         | 
| 116 | 
            -
            	 | 
| 117 | 
            -
             | 
| 136 | 
            +
            	mdbx_env_set_maxdbs( db->env, db->settings.max_collections );
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            	/* Customize the maximum number of simultaneous readers. */
         | 
| 139 | 
            +
            	if ( db->settings.max_readers )
         | 
| 140 | 
            +
            		mdbx_env_set_maxreaders( db->env, db->settings.max_readers );
         | 
| 118 141 |  | 
| 119 | 
            -
            	 | 
| 142 | 
            +
            	/* Set an upper boundary (in bytes) for the database map size. */
         | 
| 143 | 
            +
            	if ( db->settings.max_size )
         | 
| 144 | 
            +
            		mdbx_env_set_geometry( db->env, -1, -1, db->settings.max_size, -1, -1, -1 );
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            	rc = mdbx_env_open( db->env, db->path, db->settings.env_flags, db->settings.mode );
         | 
| 120 147 | 
             
            	if ( rc != MDBX_SUCCESS ) {
         | 
| 121 | 
            -
            		 | 
| 148 | 
            +
            		rmdbx_close_all( db );
         | 
| 122 149 | 
             
            		rb_raise( rmdbx_eDatabaseError, "mdbx_env_open: (%d) %s", rc, mdbx_strerror(rc) );
         | 
| 123 150 | 
             
            	}
         | 
| 124 | 
            -
            	db->open = 1;
         | 
| 151 | 
            +
            	db->state.open = 1;
         | 
| 125 152 |  | 
| 126 153 | 
             
            	return Qtrue;
         | 
| 127 154 | 
             
            }
         | 
| 128 155 |  | 
| 129 156 |  | 
| 130 157 | 
             
            /*
         | 
| 131 | 
            -
             *  | 
| 132 | 
            -
             *    db.closed? #=> false
         | 
| 133 | 
            -
             *
         | 
| 134 | 
            -
             * Predicate: return true if the database environment is closed.
         | 
| 158 | 
            +
             * Open a cursor for iteration.
         | 
| 135 159 | 
             
             */
         | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 160 | 
            +
            void
         | 
| 161 | 
            +
            rmdbx_open_cursor( rmdbx_db_t *db )
         | 
| 138 162 | 
             
            {
         | 
| 139 | 
            -
            	 | 
| 140 | 
            -
            	 | 
| 163 | 
            +
            	if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
         | 
| 164 | 
            +
            	if ( ! db->txn ) rb_raise( rmdbx_eDatabaseError, "No snapshot or transaction currently open." );
         | 
| 165 | 
            +
             | 
| 166 | 
            +
            	int rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor );
         | 
| 167 | 
            +
            	if ( rc != MDBX_SUCCESS ) {
         | 
| 168 | 
            +
            		rmdbx_close_all( db );
         | 
| 169 | 
            +
            		rb_raise( rmdbx_eDatabaseError, "Unable to open cursor: (%d) %s", rc, mdbx_strerror(rc) );
         | 
| 170 | 
            +
            	}
         | 
| 171 | 
            +
             | 
| 172 | 
            +
            	return;
         | 
| 141 173 | 
             
            }
         | 
| 142 174 |  | 
| 143 175 |  | 
| 144 176 | 
             
            /*
         | 
| 145 | 
            -
             * Open a new database transaction.
         | 
| 177 | 
            +
             * Open a new database transaction.  If a transaction is already
         | 
| 178 | 
            +
             * open, this is a no-op.
         | 
| 146 179 | 
             
             *
         | 
| 147 180 | 
             
             * +rwflag+ must be either MDBX_TXN_RDONLY or MDBX_TXN_READWRITE.
         | 
| 148 181 | 
             
             */
         | 
| 149 182 | 
             
            void
         | 
| 150 | 
            -
            rmdbx_open_txn(  | 
| 183 | 
            +
            rmdbx_open_txn( rmdbx_db_t *db, int rwflag )
         | 
| 151 184 | 
             
            {
         | 
| 152 | 
            -
            	 | 
| 153 | 
            -
            	UNWRAP_DB( self, db );
         | 
| 185 | 
            +
            	if ( db->txn ) return;
         | 
| 154 186 |  | 
| 155 | 
            -
            	rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn);
         | 
| 187 | 
            +
            	int rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn );
         | 
| 156 188 | 
             
            	if ( rc != MDBX_SUCCESS ) {
         | 
| 157 | 
            -
            		 | 
| 189 | 
            +
            		rmdbx_close_all( db );
         | 
| 158 190 | 
             
            		rb_raise( rmdbx_eDatabaseError, "mdbx_txn_begin: (%d) %s", rc, mdbx_strerror(rc) );
         | 
| 159 191 | 
             
            	}
         | 
| 160 192 |  | 
| @@ -162,7 +194,7 @@ rmdbx_open_txn( VALUE self, int rwflag ) | |
| 162 194 | 
             
            		// FIXME: dbi_flags
         | 
| 163 195 | 
             
            		rc = mdbx_dbi_open( db->txn, db->subdb, MDBX_CREATE, &db->dbi );
         | 
| 164 196 | 
             
            		if ( rc != MDBX_SUCCESS ) {
         | 
| 165 | 
            -
            			 | 
| 197 | 
            +
            			rmdbx_close_all( db );
         | 
| 166 198 | 
             
            			rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
         | 
| 167 199 | 
             
            		}
         | 
| 168 200 | 
             
            	}
         | 
| @@ -171,24 +203,90 @@ rmdbx_open_txn( VALUE self, int rwflag ) | |
| 171 203 | 
             
            }
         | 
| 172 204 |  | 
| 173 205 |  | 
| 206 | 
            +
            /*
         | 
| 207 | 
            +
             * Close any existing database transaction. If there is no
         | 
| 208 | 
            +
             * active transaction, this is a no-op.  If there is a long
         | 
| 209 | 
            +
             * running transaction open, this is a no-op.
         | 
| 210 | 
            +
             *
         | 
| 211 | 
            +
             * +txnflag must either be RMDBX_TXN_ROLLBACK or RMDBX_TXN_COMMIT.
         | 
| 212 | 
            +
             */
         | 
| 213 | 
            +
            void
         | 
| 214 | 
            +
            rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
         | 
| 215 | 
            +
            {
         | 
| 216 | 
            +
            	if ( ! db->txn || db->state.retain_txn > -1 ) return;
         | 
| 217 | 
            +
             | 
| 218 | 
            +
            	switch ( txnflag ) {
         | 
| 219 | 
            +
            		case RMDBX_TXN_COMMIT:
         | 
| 220 | 
            +
            			mdbx_txn_commit( db->txn );
         | 
| 221 | 
            +
            		default:
         | 
| 222 | 
            +
            			mdbx_txn_abort( db->txn );
         | 
| 223 | 
            +
            	}
         | 
| 224 | 
            +
             | 
| 225 | 
            +
            	db->txn = 0;
         | 
| 226 | 
            +
            	return;
         | 
| 227 | 
            +
            }
         | 
| 228 | 
            +
             | 
| 229 | 
            +
             | 
| 230 | 
            +
            /*
         | 
| 231 | 
            +
             * call-seq:
         | 
| 232 | 
            +
             *    db.open_transaction( mode )
         | 
| 233 | 
            +
             *
         | 
| 234 | 
            +
             * Open a new long-running transaction.  If +mode+ is true,
         | 
| 235 | 
            +
             * it is opened read/write.
         | 
| 236 | 
            +
             *
         | 
| 237 | 
            +
             */
         | 
| 238 | 
            +
            VALUE
         | 
| 239 | 
            +
            rmdbx_rb_opentxn( VALUE self, VALUE mode )
         | 
| 240 | 
            +
            {
         | 
| 241 | 
            +
            	UNWRAP_DB( self, db );
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            	rmdbx_open_txn( db, RTEST(mode) ? MDBX_TXN_READWRITE : MDBX_TXN_RDONLY );
         | 
| 244 | 
            +
            	db->state.retain_txn = RTEST(mode) ? 1 : 0;
         | 
| 245 | 
            +
             | 
| 246 | 
            +
            	return Qtrue;
         | 
| 247 | 
            +
            }
         | 
| 248 | 
            +
             | 
| 249 | 
            +
             | 
| 250 | 
            +
            /*
         | 
| 251 | 
            +
             * call-seq:
         | 
| 252 | 
            +
             *    db.close_transaction( mode )
         | 
| 253 | 
            +
             *
         | 
| 254 | 
            +
             * Close a long-running transaction.  If +write+ is true,
         | 
| 255 | 
            +
             * the transaction is committed.  Otherwise, rolled back.
         | 
| 256 | 
            +
             *
         | 
| 257 | 
            +
             */
         | 
| 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 | 
            +
             | 
| 174 270 | 
             
            /*
         | 
| 175 271 | 
             
             * call-seq:
         | 
| 176 272 | 
             
             *    db.clear
         | 
| 177 273 | 
             
             *
         | 
| 178 | 
            -
             * Empty the  | 
| 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!
         | 
| 179 277 | 
             
             */
         | 
| 180 278 | 
             
            VALUE
         | 
| 181 279 | 
             
            rmdbx_clear( VALUE self )
         | 
| 182 280 | 
             
            {
         | 
| 183 281 | 
             
            	UNWRAP_DB( self, db );
         | 
| 184 282 |  | 
| 185 | 
            -
            	rmdbx_open_txn(  | 
| 283 | 
            +
            	rmdbx_open_txn( db, MDBX_TXN_READWRITE );
         | 
| 186 284 | 
             
            	int rc = mdbx_drop( db->txn, db->dbi, true );
         | 
| 187 285 |  | 
| 188 286 | 
             
            	if ( rc != MDBX_SUCCESS )
         | 
| 189 287 | 
             
            		rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
         | 
| 190 288 |  | 
| 191 | 
            -
            	 | 
| 289 | 
            +
            	rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
         | 
| 192 290 |  | 
| 193 291 | 
             
            	/* Refresh the environment handles. */
         | 
| 194 292 | 
             
            	rmdbx_open_env( self );
         | 
| @@ -236,72 +334,161 @@ rmdbx_val_for( VALUE self, VALUE arg ) | |
| 236 334 | 
             
            }
         | 
| 237 335 |  | 
| 238 336 |  | 
| 337 | 
            +
            /*
         | 
| 338 | 
            +
             * Deserialize and return a value.
         | 
| 339 | 
            +
             */
         | 
| 340 | 
            +
            VALUE
         | 
| 341 | 
            +
            rmdbx_deserialize( VALUE self, VALUE val )
         | 
| 342 | 
            +
            {
         | 
| 343 | 
            +
            	VALUE deserialize_proc = rb_iv_get( self, "@deserializer" );
         | 
| 344 | 
            +
            	if ( ! NIL_P( deserialize_proc ) )
         | 
| 345 | 
            +
            		val = rb_funcall( deserialize_proc, rb_intern("call"), 1, val );
         | 
| 346 | 
            +
             | 
| 347 | 
            +
            	return val;
         | 
| 348 | 
            +
            }
         | 
| 349 | 
            +
             | 
| 350 | 
            +
             | 
| 239 351 | 
             
            /* call-seq:
         | 
| 240 | 
            -
             *    db. | 
| 352 | 
            +
             *    db.each_key {|key| block } => self
         | 
| 241 353 | 
             
             *
         | 
| 242 | 
            -
             *  | 
| 354 | 
            +
             * Calls the block once for each key, returning self.
         | 
| 355 | 
            +
             * A transaction must be opened prior to use.
         | 
| 243 356 | 
             
             */
         | 
| 244 357 | 
             
            VALUE
         | 
| 245 | 
            -
             | 
| 358 | 
            +
            rmdbx_each_key( VALUE self )
         | 
| 246 359 | 
             
            {
         | 
| 247 360 | 
             
            	UNWRAP_DB( self, db );
         | 
| 248 | 
            -
            	VALUE rv = rb_ary_new();
         | 
| 249 361 | 
             
            	MDBX_val key, data;
         | 
| 250 | 
            -
            	int rc;
         | 
| 251 362 |  | 
| 252 | 
            -
            	 | 
| 363 | 
            +
            	rmdbx_open_cursor( db );
         | 
| 364 | 
            +
            	RETURN_ENUMERATOR( self, 0, 0 );
         | 
| 253 365 |  | 
| 254 | 
            -
            	 | 
| 255 | 
            -
             | 
| 366 | 
            +
            	if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
         | 
| 367 | 
            +
            		rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
         | 
| 368 | 
            +
            		while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
         | 
| 369 | 
            +
            			rb_yield( rb_str_new( key.iov_base, key.iov_len ) );
         | 
| 370 | 
            +
            		}
         | 
| 371 | 
            +
            	}
         | 
| 256 372 |  | 
| 257 | 
            -
            	 | 
| 258 | 
            -
             | 
| 259 | 
            -
             | 
| 373 | 
            +
            	mdbx_cursor_close( db->cursor );
         | 
| 374 | 
            +
            	db->cursor = NULL;
         | 
| 375 | 
            +
            	return self;
         | 
| 376 | 
            +
            }
         | 
| 377 | 
            +
             | 
| 378 | 
            +
             | 
| 379 | 
            +
            /* call-seq:
         | 
| 380 | 
            +
             *    db.each_value {|value| block } => self
         | 
| 381 | 
            +
             *
         | 
| 382 | 
            +
             * Calls the block once for each value, returning self.
         | 
| 383 | 
            +
             * A transaction must be opened prior to use.
         | 
| 384 | 
            +
             */
         | 
| 385 | 
            +
            VALUE
         | 
| 386 | 
            +
            rmdbx_each_value( VALUE self )
         | 
| 387 | 
            +
            {
         | 
| 388 | 
            +
            	UNWRAP_DB( self, db );
         | 
| 389 | 
            +
            	MDBX_val key, data;
         | 
| 390 | 
            +
             | 
| 391 | 
            +
            	rmdbx_open_cursor( db );
         | 
| 392 | 
            +
            	RETURN_ENUMERATOR( self, 0, 0 );
         | 
| 393 | 
            +
             | 
| 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 | 
            +
            		}
         | 
| 260 402 | 
             
            	}
         | 
| 261 403 |  | 
| 262 | 
            -
            	 | 
| 263 | 
            -
            	 | 
| 264 | 
            -
             | 
| 265 | 
            -
             | 
| 266 | 
            -
             | 
| 404 | 
            +
            	mdbx_cursor_close( db->cursor );
         | 
| 405 | 
            +
            	db->cursor = NULL;
         | 
| 406 | 
            +
            	return self;
         | 
| 407 | 
            +
            }
         | 
| 408 | 
            +
             | 
| 409 | 
            +
             | 
| 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.
         | 
| 415 | 
            +
             */
         | 
| 416 | 
            +
            VALUE
         | 
| 417 | 
            +
            rmdbx_each_pair( VALUE self )
         | 
| 418 | 
            +
            {
         | 
| 419 | 
            +
            	UNWRAP_DB( self, db );
         | 
| 420 | 
            +
            	MDBX_val key, data;
         | 
| 421 | 
            +
             | 
| 422 | 
            +
            	rmdbx_open_cursor( db );
         | 
| 423 | 
            +
            	RETURN_ENUMERATOR( self, 0, 0 );
         | 
| 424 | 
            +
             | 
| 425 | 
            +
            	if ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_FIRST ) == MDBX_SUCCESS ) {
         | 
| 426 | 
            +
            		VALUE rkey = rb_str_new( key.iov_base, key.iov_len );
         | 
| 427 | 
            +
            		VALUE rval = rb_str_new( data.iov_base, data.iov_len );
         | 
| 428 | 
            +
            		rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
         | 
| 429 | 
            +
             | 
| 430 | 
            +
            		while ( mdbx_cursor_get( db->cursor, &key, &data, MDBX_NEXT ) == MDBX_SUCCESS ) {
         | 
| 431 | 
            +
            			rkey = rb_str_new( key.iov_base, key.iov_len );
         | 
| 432 | 
            +
            			rval = rb_str_new( data.iov_base, data.iov_len );
         | 
| 433 | 
            +
            			rb_yield( rb_assoc_new( rkey, rmdbx_deserialize( self, rval ) ) );
         | 
| 267 434 | 
             
            		}
         | 
| 268 435 | 
             
            	}
         | 
| 269 436 |  | 
| 270 437 | 
             
            	mdbx_cursor_close( db->cursor );
         | 
| 271 438 | 
             
            	db->cursor = NULL;
         | 
| 272 | 
            -
            	 | 
| 439 | 
            +
            	return self;
         | 
| 440 | 
            +
            }
         | 
| 441 | 
            +
             | 
| 442 | 
            +
             | 
| 443 | 
            +
            /* 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 | 
            +
             | 
| 273 464 | 
             
            	return rv;
         | 
| 274 465 | 
             
            }
         | 
| 275 466 |  | 
| 276 467 |  | 
| 277 468 | 
             
            /* call-seq:
         | 
| 278 | 
            -
             *    db[ 'key' ] | 
| 469 | 
            +
             *    db[ 'key' ] => value
         | 
| 279 470 | 
             
             *
         | 
| 280 | 
            -
             *  | 
| 471 | 
            +
             * Return a single value for +key+ immediately.
         | 
| 281 472 | 
             
             */
         | 
| 282 473 | 
             
            VALUE
         | 
| 283 474 | 
             
            rmdbx_get_val( VALUE self, VALUE key )
         | 
| 284 475 | 
             
            {
         | 
| 285 476 | 
             
            	int rc;
         | 
| 286 | 
            -
            	VALUE deserialize_proc;
         | 
| 287 477 | 
             
            	UNWRAP_DB( self, db );
         | 
| 288 478 |  | 
| 289 | 
            -
            	if ( ! db->open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
         | 
| 290 | 
            -
             | 
| 291 | 
            -
            	rmdbx_open_txn( self, MDBX_TXN_RDONLY );
         | 
| 479 | 
            +
            	if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
         | 
| 480 | 
            +
            	rmdbx_open_txn( db, MDBX_TXN_RDONLY );
         | 
| 292 481 |  | 
| 293 482 | 
             
            	MDBX_val ckey = rmdbx_key_for( key );
         | 
| 294 483 | 
             
            	MDBX_val data;
         | 
| 484 | 
            +
            	VALUE rv;
         | 
| 295 485 | 
             
            	rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
         | 
| 296 | 
            -
            	 | 
| 486 | 
            +
            	rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
         | 
| 297 487 |  | 
| 298 488 | 
             
            	switch ( rc ) {
         | 
| 299 489 | 
             
            		case MDBX_SUCCESS:
         | 
| 300 | 
            -
            			 | 
| 301 | 
            -
            			 | 
| 302 | 
            -
            			if ( ! NIL_P( deserialize_proc ) )
         | 
| 303 | 
            -
            				return rb_funcall( deserialize_proc, rb_intern("call"), 1, rv );
         | 
| 304 | 
            -
            			return rv;
         | 
| 490 | 
            +
            			rv = rb_str_new( data.iov_base, data.iov_len );
         | 
| 491 | 
            +
            			return rmdbx_deserialize( self, rv );
         | 
| 305 492 |  | 
| 306 493 | 
             
            		case MDBX_NOTFOUND:
         | 
| 307 494 | 
             
            			return Qnil;
         | 
| @@ -314,9 +501,9 @@ rmdbx_get_val( VALUE self, VALUE key ) | |
| 314 501 |  | 
| 315 502 |  | 
| 316 503 | 
             
            /* call-seq:
         | 
| 317 | 
            -
             *    db[ 'key' ] = value | 
| 504 | 
            +
             *    db[ 'key' ] = value
         | 
| 318 505 | 
             
             *
         | 
| 319 | 
            -
             *  | 
| 506 | 
            +
             * Set a single value for +key+.
         | 
| 320 507 | 
             
             */
         | 
| 321 508 | 
             
            VALUE
         | 
| 322 509 | 
             
            rmdbx_put_val( VALUE self, VALUE key, VALUE val )
         | 
| @@ -324,9 +511,8 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val ) | |
| 324 511 | 
             
            	int rc;
         | 
| 325 512 | 
             
            	UNWRAP_DB( self, db );
         | 
| 326 513 |  | 
| 327 | 
            -
            	if ( ! db->open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
         | 
| 328 | 
            -
             | 
| 329 | 
            -
            	rmdbx_open_txn( self, MDBX_TXN_READWRITE );
         | 
| 514 | 
            +
            	if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
         | 
| 515 | 
            +
            	rmdbx_open_txn( db, MDBX_TXN_READWRITE );
         | 
| 330 516 |  | 
| 331 517 | 
             
            	MDBX_val ckey = rmdbx_key_for( key );
         | 
| 332 518 |  | 
| @@ -341,7 +527,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val ) | |
| 341 527 | 
             
            		rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
         | 
| 342 528 | 
             
            	}
         | 
| 343 529 |  | 
| 344 | 
            -
            	 | 
| 530 | 
            +
            	rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
         | 
| 345 531 |  | 
| 346 532 | 
             
            	switch ( rc ) {
         | 
| 347 533 | 
             
            		case MDBX_SUCCESS:
         | 
| @@ -356,38 +542,85 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val ) | |
| 356 542 |  | 
| 357 543 | 
             
            /*
         | 
| 358 544 | 
             
             * call-seq:
         | 
| 359 | 
            -
             *    db. | 
| 360 | 
            -
             * | 
| 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 | 
            +
             | 
| 557 | 
            +
            	return rmdbx_gather_stats( db );
         | 
| 558 | 
            +
            }
         | 
| 559 | 
            +
             | 
| 560 | 
            +
             | 
| 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)
         | 
| 361 566 | 
             
             *
         | 
| 362 | 
            -
             *  | 
| 363 | 
            -
             *  | 
| 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
         | 
| 364 576 | 
             
             *
         | 
| 365 577 | 
             
             */
         | 
| 366 578 | 
             
            VALUE
         | 
| 367 579 | 
             
            rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
         | 
| 368 580 | 
             
            {
         | 
| 369 581 | 
             
            	UNWRAP_DB( self, db );
         | 
| 370 | 
            -
            	VALUE subdb;
         | 
| 582 | 
            +
            	VALUE subdb, block;
         | 
| 583 | 
            +
            	char *prev_db = NULL;
         | 
| 371 584 |  | 
| 372 | 
            -
            	rb_scan_args( argc, argv, "01", &subdb );
         | 
| 585 | 
            +
            	rb_scan_args( argc, argv, "01&", &subdb, &block );
         | 
| 373 586 | 
             
            	if ( argc == 0 ) {
         | 
| 374 587 | 
             
            		if ( db->subdb == NULL ) return Qnil;
         | 
| 375 588 | 
             
            		return rb_str_new_cstr( db->subdb );
         | 
| 376 589 | 
             
            	}
         | 
| 377 590 |  | 
| 378 | 
            -
            	 | 
| 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 | 
            +
            	}
         | 
| 605 | 
            +
             | 
| 379 606 | 
             
            	db->subdb = NIL_P( subdb ) ? NULL : StringValueCStr( subdb );
         | 
| 607 | 
            +
            	rmdbx_close_dbi( db );
         | 
| 380 608 |  | 
| 381 | 
            -
            	/* | 
| 382 | 
            -
            	 * the new collection on next access.
         | 
| 383 | 
            -
            	 *
         | 
| 609 | 
            +
            	/*
         | 
| 384 610 | 
             
            	  FIXME:  Immediate transaction write to auto-create new env?
         | 
| 385 611 | 
             
            	  Fetching from here at the moment causes an error if you
         | 
| 386 | 
            -
            	  haven't written anything yet.
         | 
| 612 | 
            +
            	  haven't written anything to the new collection yet.
         | 
| 387 613 | 
             
            	 */
         | 
| 388 | 
            -
             | 
| 389 | 
            -
             | 
| 390 | 
            -
             | 
| 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 );
         | 
| 391 624 | 
             
            	}
         | 
| 392 625 |  | 
| 393 626 | 
             
            	return self;
         | 
| @@ -395,25 +628,20 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self ) | |
| 395 628 |  | 
| 396 629 |  | 
| 397 630 | 
             
            /*
         | 
| 398 | 
            -
             *  | 
| 631 | 
            +
             * Open an existing (or create a new) mdbx database at filesystem
         | 
| 632 | 
            +
             * +path+.  In block form, the database is automatically closed.
         | 
| 633 | 
            +
             *
         | 
| 399 634 | 
             
             *    MDBX::Database.open( path ) -> db
         | 
| 400 635 | 
             
             *    MDBX::Database.open( path, options ) -> db
         | 
| 401 636 | 
             
             *    MDBX::Database.open( path, options ) do |db|
         | 
| 402 | 
            -
             * | 
| 637 | 
            +
             *        db...
         | 
| 403 638 | 
             
             *    end
         | 
| 404 639 | 
             
             *
         | 
| 405 | 
            -
             * Open an existing (or create a new) mdbx database at filesystem
         | 
| 406 | 
            -
             * +path+.  In block form, the database is automatically closed.
         | 
| 407 | 
            -
             *
         | 
| 408 640 | 
             
             */
         | 
| 409 641 | 
             
            VALUE
         | 
| 410 642 | 
             
            rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
         | 
| 411 643 | 
             
            {
         | 
| 412 | 
            -
            	int mode            = 0644;
         | 
| 413 | 
            -
            	int max_collections = 0;
         | 
| 414 | 
            -
            	int env_flags       = MDBX_ENV_DEFAULTS;
         | 
| 415 644 | 
             
            	VALUE path, opts, opt;
         | 
| 416 | 
            -
             | 
| 417 645 | 
             
            	rb_scan_args( argc, argv, "11", &path, &opts );
         | 
| 418 646 |  | 
| 419 647 | 
             
            	/* Ensure options is a hash if it was passed in.
         | 
| @@ -426,52 +654,59 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self ) | |
| 426 654 | 
             
            	}
         | 
| 427 655 | 
             
            	rb_hash_freeze( opts );
         | 
| 428 656 |  | 
| 657 | 
            +
            	/* Initialize the DB vals.
         | 
| 658 | 
            +
            	 */
         | 
| 659 | 
            +
            	UNWRAP_DB( self, db );
         | 
| 660 | 
            +
            	db->env    = NULL;
         | 
| 661 | 
            +
            	db->dbi    = 0;
         | 
| 662 | 
            +
            	db->txn    = NULL;
         | 
| 663 | 
            +
            	db->cursor = NULL;
         | 
| 664 | 
            +
            	db->path   = StringValueCStr( path );
         | 
| 665 | 
            +
            	db->subdb  = NULL;
         | 
| 666 | 
            +
            	db->state.open       = 0;
         | 
| 667 | 
            +
            	db->state.retain_txn = -1;
         | 
| 668 | 
            +
            	db->settings.env_flags       = MDBX_ENV_DEFAULTS;
         | 
| 669 | 
            +
            	db->settings.mode            = 0644;
         | 
| 670 | 
            +
            	db->settings.max_collections = 0;
         | 
| 671 | 
            +
            	db->settings.max_readers     = 0;
         | 
| 672 | 
            +
            	db->settings.max_size        = 0;
         | 
| 673 | 
            +
             | 
| 429 674 | 
             
            	/* Options setup, overrides.
         | 
| 430 675 | 
             
            	 */
         | 
| 431 676 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("mode") ) );
         | 
| 432 | 
            -
            	if ( ! NIL_P(opt) ) mode = FIX2INT( opt );
         | 
| 677 | 
            +
            	if ( ! NIL_P(opt) ) db->settings.mode = FIX2INT( opt );
         | 
| 433 678 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_collections") ) );
         | 
| 434 | 
            -
            	if ( ! NIL_P(opt) ) max_collections = FIX2INT( opt );
         | 
| 679 | 
            +
            	if ( ! NIL_P(opt) ) db->settings.max_collections = FIX2INT( opt );
         | 
| 680 | 
            +
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_readers") ) );
         | 
| 681 | 
            +
            	if ( ! NIL_P(opt) ) db->settings.max_readers = FIX2INT( opt );
         | 
| 682 | 
            +
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_size") ) );
         | 
| 683 | 
            +
            	if ( ! NIL_P(opt) ) db->settings.max_size = NUM2LONG( opt );
         | 
| 435 684 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("nosubdir") ) );
         | 
| 436 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOSUBDIR;
         | 
| 685 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOSUBDIR;
         | 
| 437 686 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("readonly") ) );
         | 
| 438 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_RDONLY;
         | 
| 687 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_RDONLY;
         | 
| 439 688 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("exclusive") ) );
         | 
| 440 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_EXCLUSIVE;
         | 
| 689 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_EXCLUSIVE;
         | 
| 441 690 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("compat") ) );
         | 
| 442 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_ACCEDE;
         | 
| 691 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_ACCEDE;
         | 
| 443 692 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("writemap") ) );
         | 
| 444 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_WRITEMAP;
         | 
| 693 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_WRITEMAP;
         | 
| 445 694 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_threadlocal") ) );
         | 
| 446 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOTLS;
         | 
| 695 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOTLS;
         | 
| 447 696 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_readahead") ) );
         | 
| 448 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_NORDAHEAD;
         | 
| 697 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NORDAHEAD;
         | 
| 449 698 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_memory_init") ) );
         | 
| 450 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOMEMINIT;
         | 
| 699 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMEMINIT;
         | 
| 451 700 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("coalesce") ) );
         | 
| 452 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_COALESCE;
         | 
| 701 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_COALESCE;
         | 
| 453 702 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("lifo_reclaim") ) );
         | 
| 454 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_LIFORECLAIM;
         | 
| 703 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_LIFORECLAIM;
         | 
| 455 704 | 
             
            	opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_metasync") ) );
         | 
| 456 | 
            -
            	if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOMETASYNC;
         | 
| 705 | 
            +
            	if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMETASYNC;
         | 
| 457 706 |  | 
| 458 707 | 
             
            	/* Duplicate keys, on mdbx_dbi_open, maybe set here? */
         | 
| 459 708 | 
             
            	/* MDBX_DUPSORT = UINT32_C(0x04), */
         | 
| 460 709 |  | 
| 461 | 
            -
            	/* Initialize the DB vals.
         | 
| 462 | 
            -
            	 */
         | 
| 463 | 
            -
            	UNWRAP_DB( self, db );
         | 
| 464 | 
            -
            	db->env       = NULL;
         | 
| 465 | 
            -
            	db->dbi       = 0;
         | 
| 466 | 
            -
            	db->txn       = NULL;
         | 
| 467 | 
            -
            	db->cursor    = NULL;
         | 
| 468 | 
            -
            	db->env_flags = env_flags;
         | 
| 469 | 
            -
            	db->mode      = mode;
         | 
| 470 | 
            -
            	db->max_collections = max_collections;
         | 
| 471 | 
            -
            	db->path      = StringValueCStr( path );
         | 
| 472 | 
            -
            	db->open      = 0;
         | 
| 473 | 
            -
            	db->subdb     = NULL;
         | 
| 474 | 
            -
             | 
| 475 710 | 
             
            	/* Set instance variables.
         | 
| 476 711 | 
             
            	 */
         | 
| 477 712 | 
             
            	rb_iv_set( self, "@path", path );
         | 
| @@ -501,11 +736,21 @@ rmdbx_init_database() | |
| 501 736 | 
             
            	rb_define_method( rmdbx_cDatabase, "close", rmdbx_close, 0 );
         | 
| 502 737 | 
             
            	rb_define_method( rmdbx_cDatabase, "reopen", rmdbx_open_env, 0 );
         | 
| 503 738 | 
             
            	rb_define_method( rmdbx_cDatabase, "closed?", rmdbx_closed_p, 0 );
         | 
| 739 | 
            +
            	rb_define_method( rmdbx_cDatabase, "in_transaction?", rmdbx_in_transaction_p, 0 );
         | 
| 504 740 | 
             
            	rb_define_method( rmdbx_cDatabase, "clear", rmdbx_clear, 0 );
         | 
| 505 | 
            -
            	rb_define_method( rmdbx_cDatabase, " | 
| 741 | 
            +
            	rb_define_method( rmdbx_cDatabase, "each_key", rmdbx_each_key, 0 );
         | 
| 742 | 
            +
            	rb_define_method( rmdbx_cDatabase, "each_value", rmdbx_each_value, 0 );
         | 
| 743 | 
            +
            	rb_define_method( rmdbx_cDatabase, "each_pair", rmdbx_each_pair, 0 );
         | 
| 744 | 
            +
            	rb_define_method( rmdbx_cDatabase, "length", rmdbx_length, 0 );
         | 
| 506 745 | 
             
            	rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
         | 
| 507 746 | 
             
            	rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
         | 
| 508 747 |  | 
| 748 | 
            +
            	/* Manually open/close transactions from ruby. */
         | 
| 749 | 
            +
            	rb_define_protected_method( rmdbx_cDatabase, "open_transaction",  rmdbx_rb_opentxn, 1 );
         | 
| 750 | 
            +
            	rb_define_protected_method( rmdbx_cDatabase, "close_transaction", rmdbx_rb_closetxn, 1 );
         | 
| 751 | 
            +
             | 
| 752 | 
            +
            	rb_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
         | 
| 753 | 
            +
             | 
| 509 754 | 
             
            	rb_require( "mdbx/database" );
         | 
| 510 755 | 
             
            }
         | 
| 511 756 |  |