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
|
|