factbase 0.0.46 → 0.0.48

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: b1a1f43a45551ecd6d7dc96d1fc2d64e72b535ad92869f28b6efd1c2aafa6619
4
- data.tar.gz: 716e6a3a3e44f7f91249feec8e385463eba0a1cc9ec695f361367d2ed6e0a5eb
3
+ metadata.gz: 56a6a221e2344538c324a601f49ad655f4d5e3b6682b5e717e8f798a6eb46030
4
+ data.tar.gz: 94bb595173fbea968c4a8e8af2b8d6cf08689127e2dfd2c2c296420659da5304
5
5
  SHA512:
6
- metadata.gz: 92de5dea89b80df3856a438d0d045ea7de3c5a607295cc7d11615bc1e4d730e4e8de4fbdd710da8d3d33046917d35ef44f6c2ef768ce8f7a2210b2e8524de224
7
- data.tar.gz: 2ef47978b1ebbe1736cf6b7b4a8d231823cb8c958bf602a015a7b4ea703448b70ec1fcfcb2e1a212b162e1f0967c088eb302f5452c135b7021caae08333212ac
6
+ metadata.gz: fe4eb95604ae3ba22d24ef01a4c86b1dcdf4c9b3ff38969cbc459653a6a72a201d317bdf86f71c22206c771deebb5511ba1c64ce014c77fdf995e18926646fa2
7
+ data.tar.gz: b48c9c7243f74713bcf5b9fbad3f0ef901aa79d107d7be78e3d08d9970e898a21f9bb4938cfaac5057edc11c53150b0934a46b125019c132906114a702c90d89
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Single-Table NoSQL In-Memory Database
1
+ # Single-Table NoSQL-ish In-Memory Database
2
2
 
3
3
  [![DevOps By Rultor.com](http://www.rultor.com/b/yegor256/factbase)](http://www.rultor.com/p/yegor256/factbase)
4
4
  [![We recommend RubyMine](https://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/)
@@ -18,7 +18,7 @@ It is possible to delete a fact, but impossible to delete a property
18
18
  from a fact.
19
19
 
20
20
  **ATTENTION**: The current implemention is naive and,
21
- because of that, very slow. I will be very happy
21
+ because of that, **very slow**. I will be very happy
22
22
  if you suggest a better implementation without the change of the interface.
23
23
  The `Factbase::query()` method is what mostly needs performance optimization:
24
24
  currently it simply iterates through all facts in the factbase in order
data/lib/factbase/fact.rb CHANGED
@@ -72,12 +72,11 @@ class Factbase::Fact
72
72
  before = @map[kk]
73
73
  return if before == v
74
74
  if before.nil?
75
- @map[kk] = v
76
- return
75
+ @map[kk] = [v]
76
+ else
77
+ @map[kk] << v
78
+ @map[kk].uniq!
77
79
  end
78
- @map[kk] = [@map[kk]] unless @map[kk].is_a?(Array)
79
- @map[kk] << v
80
- @map[kk].uniq!
81
80
  end
82
81
  nil
83
82
  elsif k == '[]'
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../factbase'
24
+
25
+ # Make maps suitable for printing.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ class Factbase::Flatten
31
+ # Constructor.
32
+ def initialize(maps, sorter = '_id')
33
+ @maps = maps
34
+ @sorter = sorter
35
+ end
36
+
37
+ # Improve the maps.
38
+ # @return [Array<HashMap>] The hashmaps, but improved
39
+ def it
40
+ @maps
41
+ .sort_by { |m| m[@sorter] }
42
+ .map { |m| m.sort.to_h }
43
+ .map { |m| m.transform_values { |v| v.size == 1 ? v[0] : v } }
44
+ end
45
+ end
@@ -55,9 +55,9 @@ class Factbase::Looged
55
55
  def txn(this = self, &)
56
56
  before = @fb.size
57
57
  tail = nil
58
- r = @fb.txn(this) do |x|
58
+ r = @fb.txn(this) do |fbt|
59
59
  tail = Factbase::Looged.elapsed do
60
- yield x
60
+ yield fbt
61
61
  end
62
62
  end
63
63
  @loog.debug("Txn #{r ? 'modified' : 'didn\'t touch'} #{before} facts #{tail}")
data/lib/factbase/term.rb CHANGED
@@ -260,6 +260,7 @@ class Factbase::Term
260
260
  assert_args(1)
261
261
  v = by_symbol(0, fact)
262
262
  return 'nil' if v.nil?
263
+ v = v[0] if v.is_a?(Array) && v.size == 1
263
264
  v.class.to_s
264
265
  end
265
266
 
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'json'
24
24
  require_relative '../factbase'
25
+ require_relative '../factbase/flatten'
25
26
 
26
27
  # Factbase to JSON converter.
27
28
  #
@@ -44,7 +45,6 @@ class Factbase::ToJSON
44
45
  # Convert the entire factbase into JSON.
45
46
  # @return [String] The factbase in JSON format
46
47
  def json
47
- maps = Marshal.load(@fb.export)
48
- maps.sort_by { |m| m[@sorter] }.map { |m| m.sort.to_h }.to_json
48
+ Factbase::Flatten.new(Marshal.load(@fb.export), @sorter).it.to_json
49
49
  end
50
50
  end
@@ -23,6 +23,7 @@
23
23
  require 'nokogiri'
24
24
  require 'time'
25
25
  require_relative '../factbase'
26
+ require_relative '../factbase/flatten'
26
27
 
27
28
  # Factbase to XML converter.
28
29
  #
@@ -46,14 +47,13 @@ class Factbase::ToXML
46
47
  # @return [String] The factbase in XML format
47
48
  def xml
48
49
  bytes = @fb.export
49
- maps = Marshal.load(bytes)
50
50
  meta = {
51
51
  version: Factbase::VERSION,
52
52
  size: bytes.size
53
53
  }
54
54
  Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
55
55
  xml.fb(meta) do
56
- maps.sort_by { |m| m[@sorter] }.each do |m|
56
+ Factbase::Flatten.new(Marshal.load(bytes), @sorter).it.each do |m|
57
57
  xml.f_ do
58
58
  m.sort.to_h.each do |k, vv|
59
59
  if vv.is_a?(Array)
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'yaml'
24
24
  require_relative '../factbase'
25
+ require_relative '../factbase/flatten'
25
26
 
26
27
  # Factbase to YAML converter.
27
28
  #
@@ -44,7 +45,6 @@ class Factbase::ToYAML
44
45
  # Convert the entire factbase into YAML.
45
46
  # @return [String] The factbase in YAML format
46
47
  def yaml
47
- maps = Marshal.load(@fb.export)
48
- YAML.dump({ 'facts' => maps.sort_by { |m| m[@sorter] }.map { |m| m.sort.to_h } })
48
+ YAML.dump(Factbase::Flatten.new(Marshal.load(@fb.export), @sorter).it)
49
49
  end
50
50
  end
data/lib/factbase.rb CHANGED
@@ -79,7 +79,7 @@ require 'yaml'
79
79
  # License:: MIT
80
80
  class Factbase
81
81
  # Current version of the gem (changed by .rultor.yml on every release)
82
- VERSION = '0.0.46'
82
+ VERSION = '0.0.48'
83
83
 
84
84
  # An exception that may be thrown in a transaction, to roll it back.
85
85
  class Rollback < StandardError; end
@@ -91,10 +91,10 @@ class Factbase
91
91
  @mutex = Mutex.new
92
92
  end
93
93
 
94
- # Make a duplicate of this factbase.
94
+ # Make a deep duplicate of this factbase.
95
95
  # @return [Factbase] A new factbase
96
96
  def dup
97
- Factbase.new(@maps.dup)
97
+ Factbase.new(@maps.map { |m| m.transform_values(&:dup) })
98
98
  end
99
99
 
100
100
  # Size.
@@ -175,9 +175,9 @@ class Factbase
175
175
  @maps << {}
176
176
  modified = true
177
177
  end
178
- m.each do |k, v|
179
- next if @maps[i][k] == v
180
- @maps[i][k] = v
178
+ m.each do |k, vv|
179
+ next if @maps[i][k] == vv
180
+ @maps[i][k] = vv
181
181
  modified = true
182
182
  end
183
183
  end
@@ -29,6 +29,17 @@ require_relative '../../lib/factbase/fact'
29
29
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
30
  # License:: MIT
31
31
  class TestFact < Minitest::Test
32
+ def test_injects_data_correctly
33
+ map = {}
34
+ f = Factbase::Fact.new(Mutex.new, map)
35
+ f.foo = 1
36
+ f.bar = 2
37
+ f.bar = 3
38
+ assert_equal(2, map.size)
39
+ assert_equal([1], map['foo'])
40
+ assert_equal([2, 3], map['bar'])
41
+ end
42
+
32
43
  def test_simple_resetting
33
44
  map = {}
34
45
  f = Factbase::Fact.new(Mutex.new, map)
@@ -89,7 +100,7 @@ class TestFact < Minitest::Test
89
100
  f = Factbase::Fact.new(Mutex.new, map)
90
101
  f.foo = 42
91
102
  f.foo = 42
92
- assert_equal(42, map['foo'])
103
+ assert_equal([42], map['foo'])
93
104
  end
94
105
 
95
106
  def test_time_in_utc
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require_relative '../../lib/factbase'
25
+ require_relative '../../lib/factbase/flatten'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestFlatten < Minitest::Test
32
+ def test_mapping
33
+ maps = [{ 'b' => [42] }, { 'a' => 33 }, { 'c' => %w[hey you] }]
34
+ to = Factbase::Flatten.new(maps).it
35
+ assert(33, to[0]['a'])
36
+ assert(42, to[1]['b'])
37
+ assert(2, to[2]['c'].size)
38
+ end
39
+ end
@@ -57,6 +57,15 @@ class TestLooged < Minitest::Test
57
57
  assert(log.to_s.include?('modified'), log)
58
58
  end
59
59
 
60
+ def test_with_modifying_txn
61
+ log = Loog::Buffer.new
62
+ fb = Factbase::Looged.new(Factbase.new, log)
63
+ fb.insert.foo = 1
64
+ assert(!fb.txn { |fbt| fbt.query('(always)').each.to_a }, log)
65
+ assert(fb.txn { |fbt| fbt.query('(always)').each.to_a[0].foo = 42 }, log)
66
+ assert(log.to_s.include?('modified'), log)
67
+ end
68
+
60
69
  def test_with_empty_txn
61
70
  log = Loog::Buffer.new
62
71
  fb = Factbase::Looged.new(Factbase.new, log)
@@ -46,7 +46,7 @@ class TestQuery < Minitest::Test
46
46
  maps = []
47
47
  maps << { 'num' => 42, 'name' => 'Jeff' }
48
48
  maps << { 'pi' => 3.14, 'num' => [42, 66, 0], 'name' => 'peter' }
49
- maps << { 'time' => Time.now - 100, 'num' => 0 }
49
+ maps << { 'time' => Time.now - 100, 'num' => 0, 'hi' => [4] }
50
50
  {
51
51
  '(eq num 444)' => 0,
52
52
  '(eq time 0)' => 0,
@@ -56,6 +56,7 @@ class TestQuery < Minitest::Test
56
56
  '(eq pi +3.14)' => 1,
57
57
  '(not (exists hello))' => 3,
58
58
  '(eq "Integer" (type num))' => 2,
59
+ '(eq "Integer" (type hi))' => 1,
59
60
  '(when (eq num 0) (exists time))' => 2,
60
61
  '(unique num)' => 2,
61
62
  '(unique name)' => 2,
@@ -120,6 +120,7 @@ class TestTerm < Minitest::Test
120
120
  def test_type_matching
121
121
  t = Factbase::Term.new(:type, [:foo])
122
122
  assert_equal('Integer', t.evaluate(fact('foo' => 42), []))
123
+ assert_equal('Integer', t.evaluate(fact('foo' => [42]), []))
123
124
  assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3]), []))
124
125
  assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!'), []))
125
126
  assert_equal('Float', t.evaluate(fact('foo' => 3.14), []))
@@ -39,9 +39,9 @@ class TestToYAML < Minitest::Test
39
39
  fb.insert._id = 2
40
40
  to = Factbase::ToYAML.new(fb)
41
41
  yaml = YAML.load(to.yaml)
42
- assert_equal(2, yaml['facts'].size)
43
- assert_equal(42, yaml['facts'][0]['foo'][0])
44
- assert_equal(256, yaml['facts'][0]['foo'][1])
42
+ assert_equal(2, yaml.size)
43
+ assert_equal(42, yaml[0]['foo'][0])
44
+ assert_equal(256, yaml[0]['foo'][1])
45
45
  end
46
46
 
47
47
  def test_sorts_keys
@@ -23,12 +23,31 @@
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
25
  require_relative '../lib/factbase'
26
+ require_relative '../lib/factbase/rules'
27
+ require_relative '../lib/factbase/inv'
28
+ require_relative '../lib/factbase/pre'
29
+ require_relative '../lib/factbase/looged'
26
30
 
27
31
  # Factbase main module test.
28
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
33
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
34
  # License:: MIT
31
35
  class TestFactbase < Minitest::Test
36
+ def test_injects_data_correctly
37
+ maps = []
38
+ fb = Factbase.new(maps)
39
+ fb.insert
40
+ f = fb.insert
41
+ f.foo = 1
42
+ f.bar = 2
43
+ f.bar = 3
44
+ assert_equal(2, maps.size)
45
+ assert_equal(0, maps[0].size)
46
+ assert_equal(2, maps[1].size)
47
+ assert_equal([1], maps[1]['foo'])
48
+ assert_equal([2, 3], maps[1]['bar'])
49
+ end
50
+
32
51
  def test_simple_setting
33
52
  fb = Factbase.new
34
53
  fb.insert
@@ -44,6 +63,22 @@ class TestFactbase < Minitest::Test
44
63
  assert_equal(2, fb.size)
45
64
  end
46
65
 
66
+ def test_modify_via_query
67
+ fb = Factbase.new
68
+ fb.insert.bar = 1
69
+ fb.query('(exists bar)').each do |f|
70
+ f.bar = 42
71
+ assert_equal([1, 42], f['bar'])
72
+ end
73
+ found = 0
74
+ fb.query('(always)').each do |f|
75
+ assert_equal([1, 42], f['bar'])
76
+ found += 1
77
+ end
78
+ assert_equal(1, found)
79
+ assert_equal([1, 42], fb.query('(always)').each.to_a[0]['bar'])
80
+ end
81
+
47
82
  def test_serialize_and_deserialize
48
83
  f1 = Factbase.new
49
84
  f2 = Factbase.new
@@ -78,6 +113,8 @@ class TestFactbase < Minitest::Test
78
113
  assert(fb.txn(&:insert).is_a?(TrueClass))
79
114
  assert(fb.txn { |fbt| fbt.insert.bar = 42 })
80
115
  assert(!fb.txn { |fbt| fbt.query('(always)').each.to_a })
116
+ assert(fb.txn { |fbt| fbt.query('(always)').each { |f| f.hello = 33 } })
117
+ assert(fb.txn { |fbt| fbt.query('(always)').each.to_a[0].zzz = 33 })
81
118
  end
82
119
 
83
120
  def test_run_txn
@@ -98,6 +135,17 @@ class TestFactbase < Minitest::Test
98
135
  assert_equal(2, fb.size)
99
136
  end
100
137
 
138
+ def test_run_txn_via_query
139
+ fb = Factbase.new
140
+ fb.insert.foo = 1
141
+ assert(
142
+ fb.txn do |fbt|
143
+ fbt.query('(always)').each { |f| f.foo = 42 }
144
+ end
145
+ )
146
+ assert_equal([1, 42], fb.query('(always)').each.to_a[0]['foo'])
147
+ end
148
+
101
149
  def test_run_txn_with_inv
102
150
  fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
103
151
  fb.insert.bar = 3
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.46
4
+ version: 0.0.48
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-10 00:00:00.000000000 Z
11
+ date: 2024-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -102,6 +102,7 @@ files:
102
102
  - factbase.gemspec
103
103
  - lib/factbase.rb
104
104
  - lib/factbase/fact.rb
105
+ - lib/factbase/flatten.rb
105
106
  - lib/factbase/inv.rb
106
107
  - lib/factbase/looged.rb
107
108
  - lib/factbase/pre.rb
@@ -115,6 +116,7 @@ files:
115
116
  - lib/factbase/tuples.rb
116
117
  - renovate.json
117
118
  - test/factbase/test_fact.rb
119
+ - test/factbase/test_flatten.rb
118
120
  - test/factbase/test_inv.rb
119
121
  - test/factbase/test_looged.rb
120
122
  - test/factbase/test_pre.rb