rdf-lmdb 0.3.6 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bd37a5a5b62c89a2096f857dea6461798dc5bfc48d155b902979d97cf949214
4
- data.tar.gz: f332981a1cfb15aa127d3a73b1356a5664221f057944ec21febf1c8c863de261
3
+ metadata.gz: '02085577cc4df2383a38f97e53f9f29cc75e94f5e2d8655be73d457737eabfa7'
4
+ data.tar.gz: a6eb270cb9297f16cb7e079f6f46710ef1b54ced75ae83db7cbe6cbaaba4ce84
5
5
  SHA512:
6
- metadata.gz: cbda446d51f8d780de5bee1aa772a05ded8263eb820aa758c320f96cdd892b8fedb3925e409225d315bf17570604c4a63097e62bb8d9e776025042eb98b54776
7
- data.tar.gz: 50f8483a107b10593bcc527ec7f25ef5e3505ebe1d21b6ee2cde2286142bf308036130365e2e810e0a498a6e283f6cb2c17d3c2b1c4fc3f302de86fb2daa4966
6
+ metadata.gz: 4c4daa84e4a59411d8ec197527a4571f48e56b5d54b911e572d5aae09c17d391be318f6cb519233bab468b641dcb9d2e2f07e6a767678235eab1afcbaa06db31
7
+ data.tar.gz: 5a132e61243460abf1819c5bd445a3a87b783aefe445b46b37af4abe0cd5327c03ba59095427459c02646eb802c2a139b38041e94dd8a07daa8da30502949e46
data/TODO.org CHANGED
@@ -31,6 +31,7 @@
31
31
  - "consensus graphs" which extend the idea of union graphs to a shared reality for multiple users
32
32
  - "proxy graphs" that map to other systems (e.g. SQL)
33
33
  - or even other RDF stores
34
+ - (include caching)
34
35
  - statement-generating layers that do things we actually /do/ want statements in the graph for, but /generated/ rather than stored (or perhaps merely /cached/, and thus not subject to versioning)
35
36
  - e.g. "soft" inferences, stuff written in vocab specs that had no way to formally express at the time
36
37
  - I'm thinking specifically how ~?c a skos:OrderedCollection; skos:memberList (?m1 ?m2 ?mn)~ implies ~?c skos:member ?m1~ and so on.
@@ -38,7 +39,7 @@
38
39
  - e.g. stateful or aggregate statements computed from other statements
39
40
  - again this is totally doable with SHACL.
40
41
  * TODO RDF-star
41
- - at root there are terms
42
+ - the basic element is the term
42
43
  - terms can be normalized and hashed
43
44
  - each term is assigned a numeric identifier that is local to the database and not otherwise exposed
44
45
  - assume this is a native-endian ~size_t~ integer; we are not gonna screw around with portability across cpu architectures
@@ -54,7 +55,7 @@
54
55
  - so subjects and objects can now be /statements/ in addition to URIs and bnodes (and literals for objects)
55
56
  - so it shouldn't be the end of the world to make that a thing
56
57
  - albeit backward-compatibility to existing stores might be a problem
57
- - well if anybody wants to hire me te do that for them, they can
58
+ - well if anybody wants to hire me to do that for them, they can
58
59
  * TODO change history
59
60
  - anyway, that aside, what we're actually after is being able to access the state of the database at the instant of a particular transaction
60
61
  - random access is ideal
@@ -97,7 +98,8 @@
97
98
  - why not just stick a bit on the end of that as to whether it's added or removed
98
99
  - so we have ~added~ and ~removed~ tables of the form ~change id => statement id~
99
100
  - we also have i dunno, ~state~ or something of the form ~statement id => change id, bit for added/removed~
100
- ** TODO global mtime
101
+ ** TODO global modification time
102
+ - [X] temporary global mtime in stats databsae
101
103
  - which resources have been affected since this time/transaction id
102
104
  * TODO principals (multi-user)
103
105
  - each individual user gets their own quad store from their point of view
@@ -108,7 +110,7 @@
108
110
  - you can't add or delete statements in other people's slices and they can't change yours
109
111
  - though they should be able to transfer ownership of a set of statements to you somehow
110
112
  - (but the person receiving should be able to decline the transfer)
111
- ** TODO lensing
113
+ ** TODO lenses/perspectives
112
114
  - my concern here is a way to have a single repository that can support multiple users without "leaking" content from other users
113
115
  - like we /could/ just partition these on the disk but there are reasons not to do this:
114
116
  1. it's gonna be a pain in the ass for downstream applications in the best case
@@ -1,5 +1,5 @@
1
1
  module RDF
2
2
  module LMDB
3
- VERSION = "0.3.6"
3
+ VERSION = "0.4.0".freeze
4
4
  end
5
5
  end
data/lib/rdf/lmdb.rb CHANGED
@@ -88,15 +88,17 @@ module RDF
88
88
  'Cannot execute a rolled back transaction. Open a new one instead.' if
89
89
  @rolledback
90
90
 
91
- ret = if @txn
92
- @changes.apply(@repository)
93
- else
94
- wrap_txn { @changes.apply(@repository) }
95
- end
91
+ if @mutable && !@changes.empty?
92
+ ret = if @txn
93
+ @changes.apply(@repository)
94
+ else
95
+ wrap_txn { @changes.apply(@repository) }
96
+ end
96
97
 
97
- @changes = RDF::Changeset.new
98
+ @changes = RDF::Changeset.new
98
99
 
99
- ret
100
+ ret
101
+ end
100
102
  end
101
103
 
102
104
  def rollback
@@ -116,6 +118,9 @@ module RDF
116
118
  class Repository < ::RDF::Repository
117
119
  private
118
120
 
121
+ LMDB_OPTS =
122
+ %i[fixedmap nosubdir nosync rdonly nometasync writemap mapasync notls]
123
+
119
124
  # for the mapsize parameter
120
125
  UNITS = { nil => 1 }
121
126
  'kmgtpe'.split('').each_with_index do |x, i|
@@ -144,17 +149,19 @@ module RDF
144
149
  factor.to_i * UNITS[unit]
145
150
  end
146
151
 
147
- def init_lmdb dir, **options
148
- dir = Pathname(dir).expand_path
149
- dir.mkdir unless dir.exist?
152
+ def init_lmdb dir = nil, **options
153
+ dir ||= options[:dir]
154
+ raise ArgumentError, 'must pass in either dir or dir: keyword' unless dir
155
+
156
+ @dir = Pathname(dir).expand_path
157
+ @dir.mkdir unless @dir.exist?
150
158
 
151
159
  options[:mapsize] = int_bytes options[:mapsize] if options[:mapsize]
152
160
 
153
- # fire up the environment
154
- @lmdb = ::LMDB.new dir, **options
161
+ @lmdb_opts = options.slice(*LMDB_OPTS)
155
162
 
156
163
  # XXX trip over the old database layout for now
157
- dbs = @lmdb.database.keys.map(&:to_sym)
164
+ dbs = env.database.keys.map(&:to_sym)
158
165
  unless dbs.empty? or dbs.include? :int2term
159
166
  err = <<-ERR.tr_s("\n ", ' ')
160
167
  This version uses an updated (and incompatible) database layout.
@@ -163,9 +170,8 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
163
170
  raise ArgumentError, err
164
171
  end
165
172
 
166
- # databases are opened in a transaction, who knew
167
- @lmdb.transaction do # |t|
168
- @dbs = {
173
+ env.transaction? do
174
+ dbs = {
169
175
  # this is the control database, it gets no flags
170
176
  control: [],
171
177
  # actual instance data
@@ -187,7 +193,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
187
193
  # gp2stmt: [:dupsort, :dupfixed],
188
194
  # go2stmt: [:dupsort, :dupfixed],
189
195
  }.map do |name, flags|
190
- [name, @lmdb.database(name.to_s,
196
+ [name, env.database(name.to_s,
191
197
  **(flags + [:create]).map { |f| [f, true] }.to_h)]
192
198
  end.to_h
193
199
 
@@ -196,7 +202,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
196
202
 
197
203
  # t.commit
198
204
  end
199
- @lmdb.sync
205
+ env.sync
200
206
  end
201
207
 
202
208
  SPO = %i[subject predicate object].freeze
@@ -216,35 +222,42 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
216
222
  }.freeze
217
223
 
218
224
  def last_key db
219
- db = @dbs[db] if db.is_a? Symbol
220
- return nil if db.size == 0
221
- # the last entry in the database should be the highest number
222
- db.cursor { |c| c.last }.first.unpack1 ?J
225
+ env.transaction? do
226
+ db = env[db] if db.is_a? Symbol
227
+ if db.size != 0
228
+ # the last entry in the database should be the highest number
229
+ db.cursor { |c| c.last }.first.unpack1 ?J
230
+ else
231
+ nil
232
+ end
233
+ end
223
234
  end
224
235
 
225
236
  def int_for term
226
- case term
227
- when nil, RDF::Query::Variable then 0
228
- when RDF::Statement
229
- terms = term.to_a.map { |t| int_for t }
230
- return if terms.include? nil # the statement implicitly not here
231
-
232
- if raw = @dbs[:ints2stmt].get(terms.pack 'J3')
233
- raw.unpack1 ?J
234
- end
235
- when Hash # of integers
236
- if raw = @dbs[:ints2stmt].get(term.values_at(*SPO).pack 'J3')
237
- raw.unpack1 ?J
238
- end
239
- when RDF::Term
240
- thash = hash_term term
241
- if raw = @dbs[:hash2term].get(thash)
242
- raw.unpack1 ?J
243
- end
244
- when String
245
- # assume this is the hash string
246
- if raw = @dbs[:hash2term].get(term)
247
- raw.unpack1 ?J
237
+ env.transaction? true do
238
+ case term
239
+ when nil, RDF::Query::Variable then 0
240
+ when RDF::Statement
241
+ terms = term.to_a.map { |t| int_for t }
242
+ unless terms.include? nil # the statement implicitly not here
243
+ if raw = env[:ints2stmt].get(terms.pack 'J3')
244
+ raw.unpack1 ?J
245
+ end
246
+ end
247
+ when Hash # of integers
248
+ if raw = env[:ints2stmt].get(term.values_at(*SPO).pack 'J3')
249
+ raw.unpack1 ?J
250
+ end
251
+ when RDF::Term
252
+ thash = hash_term term
253
+ if raw = env[:hash2term].get(thash)
254
+ raw.unpack1 ?J
255
+ end
256
+ when String
257
+ # assume this is the hash string
258
+ if raw = env[:hash2term].get(term)
259
+ raw.unpack1 ?J
260
+ end
248
261
  end
249
262
  end
250
263
  end
@@ -252,38 +265,44 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
252
265
  def store_term term
253
266
  return 0 if term.nil?
254
267
  raise ArgumentError, 'must be a term' unless term.is_a? RDF::Term
255
- # get the hash first
256
- thash = hash_term term
257
- if ix = int_for(thash)
258
- return ix
259
- end
268
+ env.transaction? do
269
+ # get the hash first
270
+ thash = hash_term term
260
271
 
261
- # this should start with 1, not zero
262
- ix = (last_key(@dbs[:int2term]) || 0) + 1
263
- ib = [ix].pack ?J
264
- @dbs[:int2term].put ib, term.to_ntriples.to_nfc
272
+ if ix = int_for(thash)
273
+ ix
274
+ else
275
+ # this should start with 1, not zero
276
+ ix = (last_key(env[:int2term]) || 0) + 1
277
+ ib = [ix].pack ?J
278
+ env[:int2term].put ib, term.to_ntriples.to_nfc
265
279
 
266
- # we need the hash too to resolve the term the other way
267
- @dbs[:hash2term].put thash, ib
280
+ # we need the hash too to resolve the term the other way
281
+ env[:hash2term].put thash, ib
268
282
 
269
- ix # return the current index
283
+ ix # return the current index
284
+ end
285
+ end
270
286
  end
271
287
 
272
288
  def store_stmt statement, ints = nil
273
- ints ||= statement.to_h.transform_values { |v| store_term v }
274
- ik = ints.values_at(*SPO).pack 'J3'
275
- if ib = @dbs[:ints2stmt].get(ik)
276
- return ib.unpack1 ?J
277
- end
289
+ env.transaction? do
290
+ ints ||= statement.to_h.transform_values { |v| store_term v }
291
+ ik = ints.values_at(*SPO).pack 'J3'
278
292
 
279
- # this should start with 1, not zero
280
- ix = (last_key(:statement) || 0) + 1
281
- ib = [ix].pack ?J
293
+ if ib = env[:ints2stmt].get(ik)
294
+ ib.unpack1 ?J
295
+ else
296
+ # this should start with 1, not zero
297
+ ix = (last_key(:statement) || 0) + 1
298
+ ib = [ix].pack ?J
282
299
 
283
- @dbs[:statement].put ib, ik # number to triple-number
284
- @dbs[:ints2stmt].put ik, ib # triple-number to number
300
+ env[:statement].put ib, ik # number to triple-number
301
+ env[:ints2stmt].put ik, ib # triple-number to number
285
302
 
286
- ix # the index integer
303
+ ix # the index integer
304
+ end
305
+ end
287
306
  end
288
307
 
289
308
  # everything gets normalized to NFC on the way in (i
@@ -300,95 +319,104 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
300
319
  def add_one statement
301
320
  # get the integer keys for the terms and statement
302
321
  terms = statement.to_h
303
- ints = terms.transform_values { |v| store_term v }
304
- ipack = ints.transform_values { |v| [v].pack ?J }
305
- sint = store_stmt statement, ints
306
- spack = [sint].pack ?J
307
322
 
308
- # now we map the SPO indices
309
- SPO_MAP.each do |k, d|
310
- db = @dbs[d]
311
- ik = ipack[k]
312
- # note we test before inserting or lmdb will dutifully
313
- # create unlimited duplicate values and results will be wrong
314
- db.put ik, spack unless db.has? ik, spack
315
- end
323
+ env.transaction? do
324
+ ints = terms.transform_values { |v| store_term v }
325
+ ipack = ints.transform_values { |v| [v].pack ?J }
326
+ sint = store_stmt statement, ints
327
+ spack = [sint].pack ?J
328
+
329
+ # now we map the SPO indices
330
+ SPO_MAP.each do |k, d|
331
+ db = env[d]
332
+ ik = ipack[k]
333
+ # note we test before inserting or lmdb will dutifully
334
+ # create unlimited duplicate values and results will be wrong
335
+ db.put ik, spack unless db.has? ik, spack
336
+ end
316
337
 
317
- # now we do the pair indices
318
- PAIR_MAP.each do |pair, d|
319
- db = @dbs[d]
320
- ik = ipack.values_at(*pair).join
321
- db.put ik, spack unless db.has? ik, spack
322
- end
338
+ # now we do the pair indices
339
+ PAIR_MAP.each do |pair, d|
340
+ db = env[d]
341
+ ik = ipack.values_at(*pair).join
342
+ db.put ik, spack unless db.has? ik, spack
343
+ end
344
+
345
+ # associate the statement with its graph; note zero is the null graph
346
+ gint = ints[:graph_name] || 0
347
+ gpack = [gint].pack ?J
323
348
 
324
- # associate the statement with its graph; note zero is the null graph
325
- gint = ints[:graph_name] || 0
326
- gpack = [gint].pack ?J
327
- @dbs[:g2stmt].put gpack, spack unless @dbs[:g2stmt].has? gpack, spack
328
- @dbs[:stmt2g].put spack, gpack unless @dbs[:stmt2g].has? spack, gpack
349
+ env[:g2stmt].put gpack, spack unless env[:g2stmt].has? gpack, spack
350
+ env[:stmt2g].put spack, gpack unless env[:stmt2g].has? spack, gpack
351
+ end
329
352
  end
330
353
 
331
354
  def rm_one statement, scan: true
332
355
  terms = statement.to_h
333
- ints = terms.transform_values { |v| int_for v }
334
- # if none of the terms resolve, we don't have it
335
- return [] if ints.values_at(*SPO).include? nil
336
- # same goes for the statement
337
- sint = int_for(ints) or return []
338
- spack = [sint].pack ?J
339
-
340
-
341
- gint = ints[:graph_name] or return []
342
- gpack = [gint].pack ?J
343
- graphs = @dbs[:stmt2g].each_value(spack).to_a.uniq
344
-
345
- out = []
346
- unless graphs.empty?
347
- # this will dissociate the statement from the graph
348
- @dbs[:g2stmt].delete? gpack, spack
349
- @dbs[:stmt2g].delete? spack, gpack
350
-
351
- if graphs.size == 1 and graphs.first == gpack
352
- # nuke the statement if this is the only instance of it
353
- @dbs[:statement].delete? spack
354
- @dbs[:ints2stmt].delete? ints.values_at(*SPO).pack('J3')
355
-
356
- # now we nuke the indexes
357
-
358
- # first the original spo
359
- SPO_MAP.map do |k, d|
360
- ib = [ints[k]].pack ?J
361
- @dbs[d].delete? ib, spack
362
- out << ints[k]
363
- end
364
356
 
365
- # add the graph if it is not null
366
- out << terms[:graph_name] if terms[:graph_name] and gint != 0
357
+ op = -> _ = nil do
358
+ ints = terms.transform_values { |v| int_for v }
359
+ # if none of the terms resolve, we don't have it
360
+ return [] if ints.values_at(*SPO).include? nil
361
+ # same goes for the statement
362
+ sint = int_for(ints) or return []
363
+ spack = [sint].pack ?J
364
+
365
+
366
+ gint = ints[:graph_name] or return []
367
+ gpack = [gint].pack ?J
368
+ graphs = env[:stmt2g].each_value(spack).to_a.uniq
369
+
370
+ out = []
371
+ unless graphs.empty?
372
+ # this will dissociate the statement from the graph
373
+ env[:g2stmt].delete? gpack, spack
374
+ env[:stmt2g].delete? spack, gpack
375
+
376
+ if graphs.size == 1 and graphs.first == gpack
377
+ # nuke the statement if this is the only instance of it
378
+ env[:statement].delete? spack
379
+ env[:ints2stmt].delete? ints.values_at(*SPO).pack('J3')
380
+
381
+ # now we nuke the indexes
382
+
383
+ # first the original spo
384
+ SPO_MAP.map do |k, d|
385
+ ib = [ints[k]].pack ?J
386
+ env[d].delete? ib, spack
387
+ out << ints[k]
388
+ end
389
+
390
+ # add the graph if it is not null
391
+ out << terms[:graph_name] if terms[:graph_name] and gint != 0
367
392
 
368
- # and now the pair map
369
- ipack = ints.slice(*SPO).transform_values { |v| [v].pack ?J }
370
- PAIR_MAP.map do |pair, d|
371
- ib = ipack.values_at(*pair).join
372
- @dbs[d].delete? ib, spack
393
+ # and now the pair map
394
+ ipack = ints.slice(*SPO).transform_values { |v| [v].pack ?J }
395
+ PAIR_MAP.map do |pair, d|
396
+ ib = ipack.values_at(*pair).join
397
+ env[d].delete? ib, spack
398
+ end
373
399
  end
374
400
  end
375
- end
376
401
 
377
- # nuke any unused terms
378
- clean_terms out if scan
402
+ # nuke any unused terms
403
+ clean_terms out if scan
379
404
 
380
- out
405
+ out
406
+ end
407
+
408
+ env.transaction? &op
381
409
  end
382
410
 
383
411
  def clean_terms terms
384
412
  terms.map! { |t| t.is_a?(RDF::Term) ? hash_term(t) : t.to_s }.uniq
385
- @lmdb.transaction do
413
+ env.transaction? do
386
414
  terms.each do |hash|
387
415
  next if hash == NULL_SHA256
388
- next unless ib = @dbs[:hash2term].get(hash)
389
- unless SPOG_MAP.values.any? {|d| @dbs[d].get ib }
390
- @dbs[:int2term].delete? ib
391
- @dbs[:hash2term].delete? hash
416
+ next unless ib = env[:hash2term].get(hash)
417
+ unless SPOG_MAP.values.any? {|d| env[d].get ib }
418
+ env[:int2term].delete? ib
419
+ env[:hash2term].delete? hash
392
420
  end
393
421
  end
394
422
  end
@@ -399,32 +427,36 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
399
427
  statement.incomplete?
400
428
  end
401
429
 
402
- def resolve_term candidate, cache: {}, write: false
430
+ def resolve_term candidate, cache: nil, write: true
431
+ cache ||= @icache
403
432
  int = nil
404
- term = case candidate
405
- when nil then return
406
- when Integer
407
- int = candidate
408
- return if int == 0
409
- return cache[int] if cache[int]
410
- str = [int].pack ?J
411
- @dbs[:int2term][str] or return
412
- when String
413
- int = candidate.unpack1 ?J
414
- str = [int].pack ?J
415
- if candidate == str
416
- return if int == 0
417
- return cache[int] if cache[int]
418
- @dbs[:int2term][str] or return
419
- else
420
- return if candidate == NULL_SHA256
421
- str = @dbs[:hash2term][candidate] or return
422
- @dbs[:int2term][str] or return
423
- int = str.unpack1 ?J
424
- end
425
- else
426
- raise ArgumentError, 'not an integer or a string'
427
- end
433
+
434
+ term = env.transaction? do
435
+ case candidate
436
+ when nil then return
437
+ when Integer
438
+ int = candidate
439
+ return if int == 0
440
+ return cache[int] if cache[int]
441
+ str = [int].pack ?J
442
+ env[:int2term][str] or return
443
+ when String
444
+ int = candidate.unpack1 ?J
445
+ str = [int].pack ?J
446
+ if candidate == str
447
+ return if int == 0
448
+ return cache[int] if cache[int]
449
+ env[:int2term][str] or return
450
+ else
451
+ return if candidate == NULL_SHA256
452
+ str = env[:hash2term][candidate] or return
453
+ [:int2term][str] or return
454
+ int = str.unpack1 ?J
455
+ end
456
+ else
457
+ raise ArgumentError, 'not an integer or a string'
458
+ end
459
+ end
428
460
 
429
461
  term.force_encoding 'utf-8'
430
462
 
@@ -433,22 +465,17 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
433
465
  term
434
466
  end
435
467
 
436
- def split_fixed string, length
437
- string = string.dup
468
+ def resolve_terms string, cache: nil, write: true, hash: false
469
+ cache ||= @icache
470
+ # we create a sequence because they may be duplicate
438
471
  seq = []
439
- until string.empty?
440
- seq << string.slice!(0, length)
441
- end
442
- seq
443
- end
444
472
 
445
- def resolve_terms string, cache: {}, write: false, hash: false
446
- seq = []
447
- out = string.unpack('J*').map do |i|
448
- seq << i
449
- j = resolve_term(i, cache: cache, write: write)
450
- [i, j]
451
- end.to_h
473
+ out = env.transaction? do
474
+ string.unpack('J*').each_with_object({}) do |i, h|
475
+ seq << i
476
+ h[i] ||= resolve_term(i, cache: cache, write: write)
477
+ end
478
+ end
452
479
 
453
480
  # if we aren't returning a hash, make sure the result is
454
481
  # returned in order
@@ -456,12 +483,12 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
456
483
  end
457
484
 
458
485
  def each_maybe_with_graph has_graph = false, &block
459
- body = -> do
460
- cache = {}
461
- @dbs[:statement].each do |spack, spo|
486
+ op = -> _ = nil do
487
+ cache = @icache
488
+ env[:statement].each do |spack, spo|
462
489
  spo = resolve_terms spo, cache: cache, write: true
463
490
 
464
- @dbs[:stmt2g].each_value spack do |gpack|
491
+ env[:stmt2g].each_value spack do |gpack|
465
492
  gint = gpack.unpack1 ?J
466
493
  # setting the default graph to zero seems like something
467
494
  # i would do
@@ -472,11 +499,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
472
499
  end
473
500
  end
474
501
 
475
- @lmdb.transaction do
476
- body.call
477
- end
478
-
479
- #@lmdb.active_txn ? body.call : @lmdb.transaction(true, &body)
502
+ env.transaction? true, &op
480
503
  end
481
504
 
482
505
  def check_triple_quad arg, name: :triple, quad: false
@@ -531,7 +554,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
531
554
  ihash = thash.transform_values { |v| int_for v }
532
555
  cache = thash.keys.map { |k| [ihash[k], thash[k]] }.to_h
533
556
 
534
- body = -> _ = nil do
557
+ op = -> _ = nil do
535
558
  # if the graph is nonexistent there is nothing to show
536
559
  return if thash[:graph_name] and !ihash[:graph_name]
537
560
 
@@ -542,17 +565,17 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
542
565
  stmt = RDF::Statement.new(**thash)
543
566
  sint = int_for(stmt) or return
544
567
  spack = [sint].pack ?J
545
- first = @dbs[:statement].get(spack) or return
568
+ first = env[:statement].get(spack) or return
546
569
 
547
570
  # warn thash.inspect, ihash.inspect
548
571
 
549
572
  # note
550
573
  if gint = ihash[:graph_name]
551
574
  gpack = [gint].pack ?J
552
- return unless @dbs[:stmt2g].has? spack, gpack
575
+ return unless env[:stmt2g].has? spack, gpack
553
576
  yield stmt
554
577
  else
555
- @dbs[:stmt2g].each_value spack do |gpack|
578
+ env[:stmt2g].each_value spack do |gpack|
556
579
  # return if gpack.unpack1(?J) == 0
557
580
  graph = resolve_term gpack, cache: cache, write: true
558
581
  yield RDF::Statement.from(stmt, graph_name: graph)
@@ -562,18 +585,18 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
562
585
  # if only a single component (e.g. :subject) is present then
563
586
  # we only need to check (e.g.) :s2stmt.
564
587
  pos = thash.keys.first
565
- db = @dbs[SPOG_MAP[pos]]
588
+ db = env[SPOG_MAP[pos]]
566
589
  ix = ihash[pos] or return # note ihash[pos] may be nil
567
590
  anchor = [ix].pack ?J
568
591
  return unless db.has? anchor
569
592
 
570
593
  db.each_value anchor do |spack|
571
- spo = resolve_terms @dbs[:statement][spack],
594
+ spo = resolve_terms env[:statement][spack],
572
595
  cache: cache, write: true
573
596
  if pos == :graph_name
574
597
  yield RDF::Statement(*spo, graph_name: thash[:graph_name])
575
598
  else
576
- @dbs[:stmt2g].each_value spack do |gpack|
599
+ env[:stmt2g].each_value spack do |gpack|
577
600
  gint = gpack.unpack1 ?J
578
601
  graph = resolve_term gint, cache: cache, write: true
579
602
  yield RDF::Statement(*spo, graph_name: graph)
@@ -582,7 +605,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
582
605
  end
583
606
  elsif thash.keys.count == 2 and thash[:graph_name]
584
607
  pos = (thash.keys - [:graph_name]).first
585
- db = @dbs[SPO_MAP[pos]]
608
+ db = env[SPO_MAP[pos]]
586
609
  ix = ihash[pos] or return
587
610
  anchor = [ix].pack ?J
588
611
  return unless db.has? anchor
@@ -590,10 +613,10 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
590
613
  # warn "ix: #{ix} #{ihash.inspect} orr bug here?"
591
614
 
592
615
  db.each_value anchor do |spack|
593
- spo = @dbs[:statement][spack]
616
+ spo = env[:statement][spack]
594
617
  # warn "node: #{spack.unpack1 ?J} spo: #{spo.unpack 'J*'}"
595
618
  gpack = [ihash[:graph_name]].pack ?J
596
- next unless @dbs[:stmt2g].has? spack, gpack
619
+ next unless env[:stmt2g].has? spack, gpack
597
620
  spo = resolve_terms spo
598
621
  yield RDF::Statement(*spo, graph_name: thash[:graph_name])
599
622
  end
@@ -608,7 +631,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
608
631
  (pr - thash.keys).empty? and ihash.values_at(*pr).none?(&:nil?)
609
632
  end.map do |pr, _|
610
633
  v = ihash.values_at(*pr).pack 'J2'
611
- c = @dbs[PAIR_MAP[pr]].cardinality(v)
634
+ c = env[PAIR_MAP[pr]].cardinality(v)
612
635
  [c, pr]
613
636
  end.sort do |a, b|
614
637
  a.first <=> b.first
@@ -619,17 +642,17 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
619
642
  cache: cache, write: true) if ihash[:graph_name]
620
643
 
621
644
  ib = ihash.values_at(*pair).pack 'J2'
622
- @dbs[PAIR_MAP[pair]].each_value ib do |spack|
623
- spo = resolve_terms @dbs[:statement][spack],
645
+ env[PAIR_MAP[pair]].each_value ib do |spack|
646
+ spo = resolve_terms env[:statement][spack],
624
647
  cache: cache, write: true
625
648
 
626
649
  if ihash[:graph_name]
627
650
  # warn g, ihash.inspect
628
651
  gpack = [ihash[:graph_name]].pack ?J
629
- next unless @dbs[:stmt2g].has? spack, gpack
652
+ next unless env[:stmt2g].has? spack, gpack
630
653
  yield RDF::Statement(*spo, graph_name: g)
631
654
  else
632
- @dbs[:stmt2g].each_value spack do |gpack|
655
+ env[:stmt2g].each_value spack do |gpack|
633
656
  gint = gpack.unpack1 ?J
634
657
  g = resolve_term gint, cache: cache, write: true
635
658
  yield RDF::Statement(*spo, graph_name: g)
@@ -639,21 +662,14 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
639
662
  end
640
663
  end
641
664
 
642
- @lmdb.active_txn ? body.call : @lmdb.transaction(true, &body)
643
-
644
- # ret = nil
645
- # @lmdb.transaction do
646
- # ret = body.call
647
- # end
648
-
649
- # ret
665
+ env.transaction? true, &op
650
666
  end
651
667
 
652
668
  def log_mtime time = nil
653
669
  time ||= Time.now in: ?Z
654
670
  nsecs = time.utc.to_r
655
671
  nsecs = (nsecs * 10**9).numerator
656
- @lmdb.transaction { @dbs[:control]['mtime'] = [nsecs].pack ?q }
672
+ env.transaction? { env[:control]['mtime'] = [nsecs].pack ?q }
657
673
  time
658
674
  end
659
675
 
@@ -667,6 +683,8 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
667
683
  raise ArgumentError, "Invalid transaction class #{@tx_class}" unless
668
684
  @tx_class.is_a? Class and @tx_class <= DEFAULT_TX_CLASS
669
685
 
686
+ @icache = {}
687
+
670
688
  init_lmdb dir, **options
671
689
  super uri: uri, title: title, **options, &block
672
690
  end
@@ -681,14 +699,13 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
681
699
  :serializable
682
700
  end
683
701
 
684
- def path
685
- Pathname(@lmdb.path)
686
- end
702
+ attr_reader :dir
703
+
704
+ alias_method :path, :dir
687
705
 
688
706
  def clear
689
- @lmdb.transaction do
690
- @dbs.each_value { |db| db.clear }
691
- end
707
+ env.transaction? { env.databases.each { |db| env[db].clear } }
708
+
692
709
  # we do not clear the main database; that nukes the sub-databases
693
710
  # @lmdb.database.clear
694
711
  end
@@ -698,7 +715,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
698
715
  end
699
716
 
700
717
  def close
701
- @lmdb.close
718
+ env.close
702
719
  end
703
720
 
704
721
  # Return a {::Time} object representing when the store was last written.
@@ -706,7 +723,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
706
723
  # @return [Time] said modification time
707
724
  #
708
725
  def mtime
709
- if packed = @dbs[:control]['mtime']
726
+ if packed = env[:control]['mtime']
710
727
  nsecs = Rational(packed.unpack1(?q), 10 ** 9)
711
728
  Time.at nsecs, in: ?Z
712
729
  else
@@ -718,10 +735,9 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
718
735
 
719
736
  def insert_statement statement
720
737
  complete! statement
721
- @lmdb.transaction do |t|
738
+ env.transaction? do |t|
722
739
  add_one statement
723
740
  log_mtime
724
- t.commit # cargo cult?
725
741
  end
726
742
  nil
727
743
  end
@@ -731,7 +747,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
731
747
  # because we want the transaction on the outside.
732
748
  complete! statement
733
749
  # warn "WTF LOL #{statement.inspect}"
734
- @lmdb.transaction do |t|
750
+ env.transaction? do |t|
735
751
  if statement.variable?
736
752
  query(statement).each { |stmt| rm_one stmt }
737
753
  else
@@ -739,14 +755,12 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
739
755
  end
740
756
 
741
757
  log_mtime
742
-
743
- t.commit
744
758
  end
745
759
  nil
746
760
  end
747
761
 
748
762
  def insert_statements statements
749
- @lmdb.transaction do
763
+ env.transaction? do
750
764
  statements.each do |statement|
751
765
  complete! statement
752
766
  add_one statement
@@ -758,7 +772,7 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
758
772
  end
759
773
 
760
774
  def delete_statements statements
761
- @lmdb.transaction do |t|
775
+ env.transaction? do
762
776
  hashes = []
763
777
 
764
778
  # we don't know what's in here but it may contain statements
@@ -781,8 +795,6 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
781
795
  clean_terms hashes.uniq
782
796
 
783
797
  log_mtime
784
-
785
- t.commit
786
798
  end
787
799
 
788
800
  nil
@@ -791,82 +803,92 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
791
803
  # data retrieval
792
804
 
793
805
  def each &block
794
- return enum_for :each unless block_given?
806
+ return enum_for :each unless block
795
807
 
796
808
  each_maybe_with_graph(&block)
797
809
  end
798
810
 
799
811
  def each_subject &block
800
- return enum_for :each_subject unless block_given?
801
- @dbs[:s2stmt].cursor do |c|
802
- while (k, _ = c.next true)
803
- yield resolve_term k
812
+ return enum_for :each_subject unless block
813
+ env.transaction? true do
814
+ env[:s2stmt].cursor do |c|
815
+ while (k, _ = c.next true)
816
+ block.call(resolve_term k)
817
+ end
804
818
  end
805
819
  end
806
820
  end
807
821
 
808
822
  def each_predicate &block
809
- return enum_for :each_predicate unless block_given?
810
- @dbs[:p2stmt].cursor do |c|
811
- while (k, _ = c.next true)
812
- yield resolve_term k
823
+ return enum_for :each_predicate unless block
824
+ env.transaction? true do
825
+ env[:p2stmt].cursor do |c|
826
+ while (k, _ = c.next true)
827
+ block.call(resolve_term k)
828
+ end
813
829
  end
814
830
  end
815
831
  end
816
832
 
817
833
  def each_object &block
818
- return enum_for :each_object unless block_given?
819
- @dbs[:o2stmt].cursor do |c|
820
- while (k, _ = c.next true)
821
- yield resolve_term k
834
+ return enum_for :each_object unless block
835
+
836
+ env.transaction? true do
837
+ env[:o2stmt].cursor do |c|
838
+ while (k, _ = c.next true)
839
+ block.call(resolve_term k)
840
+ end
822
841
  end
823
842
  end
824
843
  end
825
844
 
826
845
  def each_graph &block
827
- return enum_for :each_graph unless block_given?
828
- @dbs[:g2stmt].cursor do |c|
829
- while (k, _ = c.next true)
830
- yield RDF::Graph.new(graph_name: resolve_term(k), data: self)
846
+ return enum_for :each_graph unless block
847
+
848
+ env.transaction? true do
849
+ env[:g2stmt].cursor do |c|
850
+ while (k, _ = c.next true)
851
+ block.call RDF::Graph.new(graph_name: resolve_term(k), data: self)
852
+ end
831
853
  end
832
854
  end
833
855
  end
834
856
 
835
857
  def each_term &block
836
- return enum_for :each_term unless block_given?
837
- @dbs[:int2term].cursor do |c|
838
- while (_, v = c.next)
839
- # yield RDF::NTriples::Reader.unserialize v
840
- v.force_encoding 'utf-8'
841
- yield RDF::NTriples::Reader.parse_object(v, intern: true)
858
+ return enum_for :each_term unless block
859
+
860
+ env.transaction? true do
861
+ env[:int2term].cursor do |c|
862
+ while (_, v = c.next)
863
+ # yield RDF::NTriples::Reader.unserialize v
864
+ v.force_encoding 'utf-8'
865
+ block.call RDF::NTriples::Reader.parse_object(v, intern: true)
866
+ end
842
867
  end
843
868
  end
844
869
  end
845
870
 
846
871
  def project_graph graph_name, &block
847
- return enum_for :project_graph, graph_name unless block_given?
848
- body = -> do
872
+ return enum_for :project_graph, graph_name unless block
873
+
874
+ op = -> _ = nil do
849
875
  gint = graph_name ? int_for(graph_name) : 0
850
876
  return unless gint
851
877
  gpack = [gint].pack ?J
852
878
  cache = {}
853
- @dbs[:statement].each do |spack, spo|
854
- next unless @dbs[:stmt2g].has? spack, gpack
879
+ env[:statement].each do |spack, spo|
880
+ next unless env[:stmt2g].has? spack, gpack
855
881
  spo = resolve_terms spo, cache: cache, write: true
856
882
 
857
883
  block.call RDF::Statement(*spo, graph_name: graph_name)
858
884
  end
859
885
  end
860
886
 
861
- @lmdb.transaction do
862
- body.call
863
- end
864
-
865
- #@lmdb.active_txn ? body.call : @lmdb.transaction(true, &body)
887
+ env.transaction? true, &op
866
888
  end
867
889
 
868
890
  def count
869
- @dbs[:stmt2g].size
891
+ env[:stmt2g].size
870
892
  end
871
893
 
872
894
  def empty?
@@ -881,17 +903,17 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
881
903
 
882
904
  def delete_insert deletes, inserts
883
905
  ret = nil
884
- @lmdb.transaction { ret = super(deletes, inserts) }
906
+ ret = env.transaction? { super(deletes, inserts) }
885
907
  commit_transaction # this is to satiate the test suite
886
908
  ret
887
909
  end
888
910
 
889
911
  def env
890
- @lmdb
912
+ (@lmdb ||= {})[Process.pid] ||= ::LMDB.new dir, @lmdb_opts
891
913
  end
892
914
 
893
915
  def transaction mutable: false, &block
894
- return begin_transaction mutable: mutable unless block_given?
916
+ return begin_transaction mutable: mutable unless block
895
917
 
896
918
  begin
897
919
  begin_transaction mutable: mutable, &block
@@ -912,39 +934,61 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
912
934
  def has_graph? graph_name
913
935
  raise ArgumentError, 'graph_name must be an RDF::Term' unless
914
936
  graph_name.is_a? RDF::Term
915
- int = int_for(graph_name) or return
916
- pack = [int].pack ?J
917
- @dbs[:g2stmt].has? pack
937
+
938
+ env.transaction? true do
939
+ if int = int_for(graph_name)
940
+ pack = [int].pack ?J
941
+ env[:g2stmt].has? pack
942
+ end
943
+ end
918
944
  end
919
945
 
920
946
  def has_subject? subject
921
947
  raise ArgumentError, 'subject must be an RDF::Term' unless
922
948
  subject.is_a? RDF::Term
923
- int = int_for(subject) or return
924
- pack = [int].pack ?J
925
- @dbs[:s2stmt].has? pack
949
+
950
+ env.transaction? true do
951
+ if int = int_for(subject)
952
+ pack = [int].pack ?J
953
+ # XXX fix this upstream
954
+ !!env[:s2stmt].has?(pack)
955
+ else
956
+ false
957
+ end
958
+ end
926
959
  end
927
960
 
928
961
  def has_predicate? predicate
929
962
  raise ArgumentError, 'predicate must be an RDF::Term' unless
930
963
  predicate.is_a? RDF::Term
931
- int = int_for(predicate) or return
932
- pack = [int].pack ?J
933
- @dbs[:p2stmt].has? pack
964
+
965
+ env.transaction? true do
966
+ if int = int_for(predicate)
967
+ pack = [int].pack ?J
968
+ env[:p2stmt].has? pack
969
+ end
970
+ end
934
971
  end
935
972
 
936
973
  def has_object? object
937
974
  raise ArgumentError, 'object must be an RDF::Term' unless
938
975
  object.is_a? RDF::Term
939
- int = int_for(object) or return
940
- pack = [int].pack ?J
941
- @dbs[:o2stmt].has? pack
976
+
977
+ env.transaction? true do
978
+ if int = int_for(object)
979
+ pack = [int].pack ?J
980
+ env[:o2stmt].has? pack
981
+ end
982
+ end
942
983
  end
943
984
 
944
985
  def has_term? term
945
986
  raise ArgumentError, 'term must be an RDF::Term' unless
946
987
  term.is_a? RDF::Term
947
- @dbs[:hash2term].has? hash_term(term)
988
+
989
+ env.transaction? true do
990
+ env[:hash2term].has? hash_term(term)
991
+ end
948
992
  end
949
993
 
950
994
  def has_triple? triple
@@ -955,7 +999,6 @@ Currently you have to dump from the old layout and reload the new one. Sorry!
955
999
  has_statement? check_triple_quad quad, quad: true
956
1000
  end
957
1001
 
958
-
959
1002
  # lol, ruby
960
1003
  end
961
1004
  end
data/rdf-lmdb.gemspec CHANGED
@@ -25,7 +25,7 @@ robust key-value store.
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  # ruby
28
- spec.required_ruby_version = '>= 2.0'
28
+ spec.required_ruby_version = '>= 3.2'
29
29
 
30
30
  # dev/test dependencies
31
31
  spec.add_development_dependency 'bundler', '~> 2'
@@ -36,5 +36,5 @@ robust key-value store.
36
36
  # stuff we use
37
37
  spec.add_runtime_dependency 'unf', '~> 0.1'
38
38
  spec.add_runtime_dependency 'rdf', '~> 3'
39
- spec.add_runtime_dependency 'lmdb', '~> 0.6', '>= 0.6.2'
39
+ spec.add_runtime_dependency 'lmdb', '~> 0', '>= 0.8.1'
40
40
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-lmdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Taylor
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
@@ -99,20 +99,20 @@ dependencies:
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '0.6'
102
+ version: '0'
103
103
  - - ">="
104
104
  - !ruby/object:Gem::Version
105
- version: 0.6.2
105
+ version: 0.8.1
106
106
  type: :runtime
107
107
  prerelease: false
108
108
  version_requirements: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: '0.6'
112
+ version: '0'
113
113
  - - ">="
114
114
  - !ruby/object:Gem::Version
115
- version: 0.6.2
115
+ version: 0.8.1
116
116
  description: |
117
117
  This module implements RDF::Repository on top of LMDB, a fast and
118
118
  robust key-value store.
@@ -147,14 +147,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
147
  requirements:
148
148
  - - ">="
149
149
  - !ruby/object:Gem::Version
150
- version: '2.0'
150
+ version: '3.2'
151
151
  required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  requirements:
153
153
  - - ">="
154
154
  - !ruby/object:Gem::Version
155
155
  version: '0'
156
156
  requirements: []
157
- rubygems_version: 3.6.3
157
+ rubygems_version: 3.6.7
158
158
  specification_version: 4
159
159
  summary: Symas LMDB back-end for RDF::Repository
160
160
  test_files: []