lmdb 0.4.7 → 0.6

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.
@@ -47,18 +47,35 @@
47
47
  Data_Get_Struct(var, Cursor, var_cur); \
48
48
  cursor_check(var_cur)
49
49
 
50
+ /*
51
+ hey yo if you can convince hyc to add a function like
52
+ mdb_txn_get_flags or even mdb_txn_is_rdonly, you could probably get
53
+ rid of these wrapper structs and a lot of complexity, cause that is
54
+ literally the only reason why they're needed
55
+
56
+ actually no we would also need to know when a txn has a child and
57
+ there is also no mdb_txn_get_child or whatever
58
+ */
59
+
50
60
  typedef struct {
51
61
  VALUE env;
52
- VALUE parent;
62
+ VALUE parent; // ignored for ro threads
63
+ VALUE child; // ditto
53
64
  VALUE thread;
54
65
  VALUE cursors;
55
66
  MDB_txn* txn;
67
+ unsigned int flags;
56
68
  } Transaction;
57
69
 
70
+ // we have an extra field `rw_txn_thread` here that acts as the
71
+ // registry for the single read-write transaction. if it's populated,
72
+ // that's the one
73
+
58
74
  typedef struct {
59
75
  MDB_env* env;
60
- VALUE thread_txn_hash;
61
- VALUE txn_thread_hash;
76
+ VALUE thread_txn_hash; /* transaction -> thread */
77
+ VALUE txn_thread_hash; /* thread -> transaction */
78
+ VALUE rw_txn_thread; /* the thread with the rw transaction */
62
79
  } Environment;
63
80
 
64
81
  typedef struct {
@@ -116,10 +133,10 @@ static void cursor_free(Cursor* cursor);
116
133
  static VALUE cursor_get(VALUE self);
117
134
  static VALUE cursor_last(VALUE self);
118
135
  static void cursor_mark(Cursor* cursor);
119
- static VALUE cursor_next(VALUE self);
136
+ static VALUE cursor_next(int argc, VALUE* argv, VALUE self);
120
137
  static VALUE cursor_prev(VALUE self);
121
138
  static VALUE cursor_put(int argc, VALUE* argv, VALUE self);
122
- static VALUE cursor_set(VALUE self, VALUE vkey);
139
+ static VALUE cursor_set(int argc, VALUE* argv, VALUE self);
123
140
  static VALUE cursor_set_range(VALUE self, VALUE vkey);
124
141
  static VALUE database_clear(VALUE self);
125
142
  static VALUE database_cursor(VALUE self);
@@ -129,6 +146,9 @@ static VALUE database_get(VALUE self, VALUE vkey);
129
146
  static void database_mark(Database* database);
130
147
  static VALUE database_put(int argc, VALUE *argv, VALUE self);
131
148
  static VALUE database_stat(VALUE self);
149
+ static VALUE database_get_flags(VALUE self);
150
+ static VALUE database_is_dupsort(VALUE self);
151
+ static VALUE database_is_dupfixed(VALUE self);
132
152
  static VALUE environment_active_txn(VALUE self);
133
153
  static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set);
134
154
  static void environment_check(Environment* environment);
data/lib/lmdb/database.rb CHANGED
@@ -11,9 +11,12 @@ module LMDB
11
11
  # puts "at #{key}: #{value}"
12
12
  # end
13
13
  def each
14
- cursor do |c|
15
- while i = c.next
16
- yield(i)
14
+ maybe_txn true do
15
+ # env.transaction do
16
+ cursor do |c|
17
+ while i = c.next
18
+ yield(i)
19
+ end
17
20
  end
18
21
  end
19
22
  end
@@ -37,13 +40,124 @@ module LMDB
37
40
  # db['b'] = 1234 #=> 1234
38
41
  # db['a'] #=> 'b'
39
42
  def []=(key, value)
40
- put(key, value)
43
+ put key, value
41
44
  value
42
45
  end
43
46
 
47
+ # Get the keys as an array.
48
+ # @return [Array] of keys.
49
+ def keys
50
+ each_key.to_a
51
+ end
52
+
53
+ # Iterate over each key in the database, skipping over duplicate records.
54
+ #
55
+ # @yield key [String] the next key in the database.
56
+ # @return [Enumerator] in lieu of a block.
57
+ def each_key(&block)
58
+ return enum_for :each_key unless block_given?
59
+ maybe_txn true do
60
+ #env.transaction do
61
+ cursor do |c|
62
+ while (rec = c.next true)
63
+ yield rec.first
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # Iterate over the duplicate values of a given key, using an
70
+ # implicit cursor. Works whether +:dupsort+ is set or not.
71
+ #
72
+ # @param key [#to_s] The key in question.
73
+ # @yield value [String] the next value associated with the key.
74
+ # @return [Enumerator] in lieu of a block.
75
+ def each_value(key, &block)
76
+ return enum_for :each_value, key unless block_given?
77
+
78
+ value = get(key) or return
79
+ unless dupsort?
80
+ yield value
81
+ return
82
+ end
83
+
84
+ maybe_txn true do
85
+ # env.transaction do
86
+ cursor do |c|
87
+ method = :set
88
+ while rec = c.send(method, key)
89
+ method = :next_range
90
+ yield rec[1]
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # Return the cardinality (number of duplicates) of a given
97
+ # key. Works whether +:dupsort+ is set or not.
98
+ # @param key [#to_s] The key in question.
99
+ # @return [Integer] The number of entries under the key.
100
+ def cardinality(key)
101
+ ret = 0
102
+ maybe_txn true do
103
+ # env.transaction do
104
+ if get key
105
+ if dupsort?
106
+ cursor do |c|
107
+ c.set key
108
+ ret = c.count
109
+ end
110
+ else
111
+ ret = 1
112
+ end
113
+ end
114
+ end
115
+ ret
116
+ end
117
+
118
+ # Test if the database has a given key (or, if opened in
119
+ # +:dupsort+, value)
120
+ def has?(key, value = nil)
121
+ v = get(key) or return false
122
+ return true if value.nil? or value.to_s == v
123
+ return false unless dupsort?
124
+
125
+ ret = false
126
+ # read-only txn was having trouble being nested inside a read-write
127
+ maybe_txn true do
128
+ # env.transaction true do
129
+ # env.transaction do
130
+ cursor { |c| ret = !!c.set(key, value) }
131
+ end
132
+ ret
133
+ end
134
+
135
+ # Delete the key (and optional value pair) if it exists; do not
136
+ # complain about missing keys.
137
+ # @param key [#to_s] The key.
138
+ # @param value [#to_s] The optional value.
139
+ def delete?(key, value = nil)
140
+ delete key, value if has? key, value
141
+ end
142
+
44
143
  # @return the number of records in this database
45
144
  def size
46
145
  stat[:entries]
47
146
  end
147
+
148
+ private
149
+
150
+ # having trouble with read-only transactions embedded in
151
+ # read-write for some reason; can't pin it down to test it yet so
152
+ # going to do this (djt; 2020-02-10)
153
+ def maybe_txn(readonly, &block)
154
+ if t = env.active_txn
155
+ yield t
156
+ else
157
+ env.transaction !!readonly do |t|
158
+ yield t
159
+ end
160
+ end
161
+ end
48
162
  end
49
163
  end
data/lib/lmdb/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LMDB
2
- VERSION = '0.4.7'
2
+ VERSION = '0.6'.freeze
3
3
  end
data/lmdb.gemspec CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |s|
19
19
  s.test_files = `git ls-files -- spec/*`.split("\n")
20
20
  s.require_paths = ['lib']
21
21
 
22
- s.required_ruby_version = ">= 1.9.3"
23
- s.add_development_dependency 'rake', "~> 10.0"
24
- s.add_development_dependency 'rake-compiler', '<=0.8.2'
22
+ s.required_ruby_version = ">= 2.4"
23
+ s.add_development_dependency 'rake', "~> 13.0"
24
+ s.add_development_dependency 'rake-compiler', '~> 1.1'
25
25
  s.add_development_dependency 'rspec', "~> 3.0"
26
+ s.add_development_dependency 'ruby_memcheck', "~> 1.0"
26
27
  end
data/spec/helper.rb CHANGED
@@ -2,6 +2,9 @@ require 'lmdb'
2
2
  require 'rspec'
3
3
  require 'fileutils'
4
4
 
5
+ # for valgrind
6
+ at_exit { GC.start }
7
+
5
8
  SPEC_ROOT = File.dirname(__FILE__)
6
9
  TEMP_ROOT = File.join(SPEC_ROOT, 'tmp')
7
10
 
data/spec/lmdb_spec.rb CHANGED
@@ -2,15 +2,15 @@
2
2
  require 'helper'
3
3
 
4
4
  describe LMDB do
5
- let(:env) { LMDB.new(path) }
5
+ let(:env) { LMDB.new(path, mapsize: 2**20) }
6
6
  after { env.close rescue nil }
7
7
 
8
8
  let(:db) { env.database }
9
9
 
10
10
  it 'has version constants' do
11
- LMDB::LIB_VERSION_MAJOR.should be_instance_of(Fixnum)
12
- LMDB::LIB_VERSION_MINOR.should be_instance_of(Fixnum)
13
- LMDB::LIB_VERSION_PATCH.should be_instance_of(Fixnum)
11
+ LMDB::LIB_VERSION_MAJOR.should be_instance_of(Integer)
12
+ LMDB::LIB_VERSION_MINOR.should be_instance_of(Integer)
13
+ LMDB::LIB_VERSION_PATCH.should be_instance_of(Integer)
14
14
  LMDB::LIB_VERSION.should be_instance_of(String)
15
15
  LMDB::VERSION.should be_instance_of(String)
16
16
  end
@@ -37,7 +37,8 @@ describe LMDB do
37
37
  end
38
38
 
39
39
  it 'accepts options' do
40
- env = LMDB::Environment.new(path, :nosync => true, :mode => 0777, :maxreaders => 777, :mapsize => 111111, :maxdbs => 666)
40
+ env = LMDB::Environment.new(path, nosync: true, mode: 0777,
41
+ maxreaders: 777, mapsize: 111111, maxdbs: 666)
41
42
  env.should be_instance_of(described_class)
42
43
  env.info[:maxreaders].should == 777
43
44
  env.info[:mapsize].should == 111111
@@ -52,22 +53,22 @@ describe LMDB do
52
53
 
53
54
  it 'should return stat' do
54
55
  stat = env.stat
55
- stat[:psize].should be_instance_of(Fixnum)
56
- stat[:depth].should be_instance_of(Fixnum)
57
- stat[:branch_pages].should be_instance_of(Fixnum)
58
- stat[:leaf_pages].should be_instance_of(Fixnum)
59
- stat[:overflow_pages].should be_instance_of(Fixnum)
60
- stat[:entries].should be_instance_of(Fixnum)
56
+ stat[:psize].should be_instance_of(Integer)
57
+ stat[:depth].should be_instance_of(Integer)
58
+ stat[:branch_pages].should be_instance_of(Integer)
59
+ stat[:leaf_pages].should be_instance_of(Integer)
60
+ stat[:overflow_pages].should be_instance_of(Integer)
61
+ stat[:entries].should be_instance_of(Integer)
61
62
  end
62
63
 
63
64
  it 'should return info' do
64
65
  info = env.info
65
- info[:mapaddr].should be_instance_of(Fixnum)
66
- info[:mapsize].should be_instance_of(Fixnum)
67
- info[:last_pgno].should be_instance_of(Fixnum)
68
- info[:last_txnid].should be_instance_of(Fixnum)
69
- info[:maxreaders].should be_instance_of(Fixnum)
70
- info[:numreaders].should be_instance_of(Fixnum)
66
+ info[:mapaddr].should be_instance_of(Integer)
67
+ info[:mapsize].should be_instance_of(Integer)
68
+ info[:last_pgno].should be_instance_of(Integer)
69
+ info[:last_txnid].should be_instance_of(Integer)
70
+ info[:maxreaders].should be_instance_of(Integer)
71
+ info[:numreaders].should be_instance_of(Integer)
71
72
  end
72
73
 
73
74
  it 'should set mapsize' do
@@ -100,7 +101,7 @@ describe LMDB do
100
101
  end
101
102
 
102
103
  describe LMDB::Transaction do
103
- subject { env}
104
+ subject { env }
104
105
 
105
106
  it 'should create transactions' do
106
107
  subject.active_txn.should == nil
@@ -170,7 +171,7 @@ describe LMDB do
170
171
  subject.active_txn.should == nil
171
172
  end
172
173
 
173
- it 'should access from transaction to environment' do
174
+ it 'should get environment' do
174
175
  env2 = nil
175
176
  env.transaction do |txn|
176
177
  env2 = txn.env
@@ -183,10 +184,18 @@ describe LMDB do
183
184
  describe LMDB::Database do
184
185
  subject { db }
185
186
 
187
+ it 'should return flags' do
188
+ subject.flags.should be_instance_of(Hash)
189
+ subject.dupsort?.should == false
190
+ subject.dupfixed?.should == false
191
+ end
192
+
186
193
  it 'should support named databases' do
187
194
  main = env.database
188
- db1 = env.database('db1', :create => true)
189
- db2 = env.database('db2', :create => true)
195
+ # funnily it complains in 2.7 unless i do this
196
+ dbopts = { create: true }
197
+ db1 = env.database 'db1', create: true # actually no it doesn't wtf
198
+ db2 = env.database 'db2', **dbopts
190
199
 
191
200
  main['key'] = '1'
192
201
  db1['key'] = '2'
@@ -201,6 +210,26 @@ describe LMDB do
201
210
  subject.get('cat').should be_nil
202
211
  subject.put('cat', 'garfield').should be_nil
203
212
  subject.get('cat').should == 'garfield'
213
+
214
+ # check for key-value pairs on non-dupsort database
215
+ subject.has?('cat', 'garfield').should == true
216
+ subject.has?('cat', 'heathcliff').should == false
217
+ end
218
+
219
+ it 'should delete by key' do
220
+ proc { subject.delete('cat') }.should raise_error(LMDB::Error::NOTFOUND)
221
+ proc { subject.delete('cat', 'garfield') }.should raise_error(LMDB::Error::NOTFOUND)
222
+
223
+ subject.put('cat', 'garfield')
224
+ subject.delete('cat').should be_nil
225
+ proc { subject.delete('cat') }.should raise_error(LMDB::Error::NOTFOUND)
226
+
227
+ subject.put('cat', 'garfield')
228
+ subject.delete('cat', 'garfield').should be_nil
229
+ proc { subject.delete('cat', 'garfield') }.should raise_error(LMDB::Error::NOTFOUND)
230
+
231
+ # soft delete
232
+ subject.delete?('cat', 'heathcliff').should be_nil
204
233
  end
205
234
 
206
235
  it 'stores key/values in same transaction' do
@@ -258,12 +287,18 @@ describe LMDB do
258
287
  db['key'].should == bin2
259
288
  end
260
289
 
261
- it 'should access environment' do
290
+ it 'should get environment' do
262
291
  main = env.database
263
- db1 = env.database('db1', :create => true)
292
+ db1 = env.database('db1', create: true)
264
293
  main.env.should == env
265
294
  db1.env.should == env
266
295
  end
296
+
297
+ it 'should iterate over/list keys' do
298
+ db['k1'] = 'v1'
299
+ db['k2'] = 'v2'
300
+ db.keys.sort.should == %w[k1 k2]
301
+ end
267
302
  end
268
303
 
269
304
  describe LMDB::Cursor do
@@ -318,6 +353,49 @@ describe LMDB do
318
353
  end
319
354
  end
320
355
 
356
+ it 'should set to a key-value pair when db is dupsort' do
357
+ dupdb = env.database 'dupsort', create: true, dupsort: true
358
+
359
+ # check flag while we're at it
360
+ dupdb.flags[:dupsort].should == true
361
+ dupdb.dupsort?.should == true
362
+ dupdb.dupfixed?.should == false
363
+
364
+ # add the no-op keyword to trigger a complaint from ruby 2.7
365
+ dupdb.put 'key1', 'value1', nodupdata: false
366
+ dupdb.put 'key1', 'value2'
367
+ dupdb.put 'key2', 'value3'
368
+ dupdb.cursor do |c|
369
+ c.set('key1', 'value2').should == ['key1', 'value2']
370
+ c.set('key1', 'value1').should == ['key1', 'value1']
371
+ c.set('key1', 'value3').should == nil
372
+ end
373
+
374
+ # this is basically an extended test of `cursor.set key, val`
375
+ dupdb.has?('key1', 'value1').should == true
376
+ dupdb.has?('key1', 'value2').should == true
377
+ dupdb.has?('key1', 'value0').should == false
378
+
379
+ # match the contents of key1
380
+ dupdb.each_value('key1').to_a.sort.should == ['value1', 'value2']
381
+
382
+ # we should have two entries for key1
383
+ dupdb.cardinality('key1').should == 2
384
+
385
+ dupdb.each_key.to_a.sort.should == ['key1', 'key2']
386
+
387
+ # XXX move this or whatever
388
+ env.transaction do |t|
389
+ dupdb.put 'key1', 'value1' unless dupdb.has? 'key1', 'value1'
390
+ end
391
+ end
392
+
393
+ it 'should complain setting a key-value pair without dupsort' do
394
+ db.cursor do |c|
395
+ proc { c.set('key1', 'value1') }.should raise_error(LMDB::Error)
396
+ end
397
+ end
398
+
321
399
  it 'should raise without block or txn' do
322
400
  proc { db.cursor.next }.should raise_error(LMDB::Error)
323
401
  end
@@ -328,10 +406,17 @@ describe LMDB do
328
406
  proc { c.next }.should raise_error(LMDB::Error)
329
407
  end
330
408
 
331
- it 'should access database' do
409
+ it 'should get database' do
332
410
  db2 = nil
333
411
  env.transaction { c = db.cursor; db2 = c.database }
334
412
  db2.should == db
335
413
  end
414
+
415
+ it 'should nest a read-only txn in a read-write' do
416
+ env.transaction do |t|
417
+ # has? opens a read-only transaction
418
+ db.put 'hurr', 'durr' unless db.has? 'hurr', 'durr'
419
+ end
420
+ end
336
421
  end
337
422
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lmdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.7
4
+ version: '0.6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Mendler
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-28 00:00:00.000000000 Z
11
+ date: 2022-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '10.0'
19
+ version: '13.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '10.0'
26
+ version: '13.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake-compiler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "<="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.8.2
33
+ version: '1.1'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "<="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.8.2
40
+ version: '1.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ruby_memcheck
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
55
69
  description: lmdb is a Ruby binding to OpenLDAP Lightning MDB.
56
70
  email: mail@daniel-mendler.de
57
71
  executables: []
@@ -66,6 +80,7 @@ files:
66
80
  - Gemfile
67
81
  - README.md
68
82
  - Rakefile
83
+ - behaviour.org
69
84
  - ext/lmdb_ext/cursor_delete_flags.h
70
85
  - ext/lmdb_ext/cursor_put_flags.h
71
86
  - ext/lmdb_ext/dbi_flags.h
@@ -95,7 +110,7 @@ homepage: https://github.com/minad/lmdb
95
110
  licenses:
96
111
  - MIT
97
112
  metadata: {}
98
- post_install_message:
113
+ post_install_message:
99
114
  rdoc_options: []
100
115
  require_paths:
101
116
  - lib
@@ -103,16 +118,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
118
  requirements:
104
119
  - - ">="
105
120
  - !ruby/object:Gem::Version
106
- version: 1.9.3
121
+ version: '2.4'
107
122
  required_rubygems_version: !ruby/object:Gem::Requirement
108
123
  requirements:
109
124
  - - ">="
110
125
  - !ruby/object:Gem::Version
111
126
  version: '0'
112
127
  requirements: []
113
- rubyforge_project:
114
- rubygems_version: 2.4.1
115
- signing_key:
128
+ rubygems_version: 3.3.11
129
+ signing_key:
116
130
  specification_version: 4
117
131
  summary: Ruby bindings to Lightning MDB
118
132
  test_files: