factbase 0.0.45 → 0.0.47

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: f50658ed4f005f217ac62d0d7915a02bda424b81b27b8fd28c04d72a87720f42
4
- data.tar.gz: 96593b68ec2ead36739708e150794a640786f76bb015476a90f80814f0f211fb
3
+ metadata.gz: 3b16ed7e90c154dfde3992d7a812c124caf6492c4f3600da01e93069e82be50f
4
+ data.tar.gz: 8cc18bca8d1a1eb3b52fdb314b38f87818796faf93d05395764d5968aa615a93
5
5
  SHA512:
6
- metadata.gz: 5595f2de94f831ed456771bfbf52efb86b2c3e29be426923dadcdee51389983ef2bedd2e41cdccb5246b91682cc7bb47164f7bf8adefbd2cf08f76812e8412a7
7
- data.tar.gz: ccbf0464b43eb277c497e27f3827c7cd3051fc94e34c727c4c258d3a999d717a600635eda52c62a874165eb3bf7804a0665edbe1bdaf7eca47aa1e568db9bc17
6
+ metadata.gz: 9376cb5f9cecf2fc5fb47296512285b8105b08fbe9683f3722995aac7bab5440a417d443f378dfc62c300f3b79d136f6365835843aabb3559eaf09f203404880
7
+ data.tar.gz: 59f4f461487d02b604b0614d571bbc10405a15c4d86ec92dedd8633d5b12a01bd92d546622aad23bd3d4caf65ff4a27ce7a18a6b1a65c6e2d54cbc0874f41d91
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,44 @@
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)
33
+ @maps = maps
34
+ end
35
+
36
+ # Improve the maps.
37
+ # @return [Array<HashMap>] The hashmaps, but improved
38
+ def it
39
+ @maps
40
+ .sort_by { |m| m[@sorter] }
41
+ .map { |m| m.sort.to_h }
42
+ .map { |m| m.transform_values { |v| v.size == 1 ? v[0] : v } }
43
+ end
44
+ end
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'time'
24
24
  require 'loog'
25
+ require_relative 'syntax'
25
26
 
26
27
  # A decorator of a Factbase, that logs all operations.
27
28
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -48,17 +49,18 @@ class Factbase::Looged
48
49
  end
49
50
 
50
51
  def query(query)
51
- Query.new(@fb.query(query), query, @loog)
52
+ Query.new(@fb, query, @loog)
52
53
  end
53
54
 
54
55
  def txn(this = self, &)
55
- r = false
56
- @fb.txn(this) do |x|
56
+ before = @fb.size
57
+ tail = nil
58
+ r = @fb.txn(this) do |fbt|
57
59
  tail = Factbase::Looged.elapsed do
58
- r = yield x
60
+ yield fbt
59
61
  end
60
- @loog.debug("Txn #{r ? 'modified' : 'did nothing'} #{tail}")
61
62
  end
63
+ @loog.debug("Txn #{r ? 'modified' : 'didn\'t touch'} #{before} facts #{tail}")
62
64
  r
63
65
  end
64
66
 
@@ -113,8 +115,8 @@ class Factbase::Looged
113
115
  # This is an internal class, it is not supposed to be instantiated directly.
114
116
  #
115
117
  class Query
116
- def initialize(query, expr, loog)
117
- @query = query
118
+ def initialize(fb, expr, loog)
119
+ @fb = fb
118
120
  @expr = expr
119
121
  @loog = loog
120
122
  end
@@ -124,7 +126,7 @@ class Factbase::Looged
124
126
  if block_given?
125
127
  r = nil
126
128
  tail = Factbase::Looged.elapsed do
127
- r = @query.each(&)
129
+ r = @fb.query(@expr).each(&)
128
130
  end
129
131
  raise ".each of #{@query.class} returned #{r.class}" unless r.is_a?(Integer)
130
132
  if r.zero?
@@ -136,7 +138,7 @@ class Factbase::Looged
136
138
  else
137
139
  array = []
138
140
  tail = Factbase::Looged.elapsed do
139
- @query.each do |f|
141
+ @fb.query(@expr).each do |f|
140
142
  array << f
141
143
  end
142
144
  end
@@ -151,14 +153,17 @@ class Factbase::Looged
151
153
 
152
154
  def delete!
153
155
  r = nil
156
+ before = @fb.size
154
157
  tail = Factbase::Looged.elapsed do
155
- r = @query.delete!
158
+ r = @fb.query(@expr).delete!
156
159
  end
157
160
  raise ".delete! of #{@query.class} returned #{r.class}" unless r.is_a?(Integer)
158
- if r.zero?
159
- @loog.debug("Nothing deleted by '#{@expr}' #{tail}")
161
+ if before.zero?
162
+ @loog.debug("There were no facts, nothing deleted by '#{@expr}' #{tail}")
163
+ elsif r.zero?
164
+ @loog.debug("No facts out of #{before} deleted by '#{@expr}' #{tail}")
160
165
  else
161
- @loog.debug("Deleted #{r} fact(s) by '#{@expr}' #{tail}")
166
+ @loog.debug("Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
162
167
  end
163
168
  r
164
169
  end
@@ -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)).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)).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)).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.45'
82
+ VERSION = '0.0.47'
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
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
+ require_relative '../../lib/factbase'
25
26
  require_relative '../../lib/factbase/looged'
26
27
 
27
28
  # Test.
@@ -45,13 +46,31 @@ class TestLooged < Minitest::Test
45
46
  end
46
47
 
47
48
  def test_with_txn
48
- fb = Factbase::Looged.new(Factbase.new, Loog::NULL)
49
+ log = Loog::Buffer.new
50
+ fb = Factbase::Looged.new(Factbase.new, log)
49
51
  assert(
50
52
  fb.txn do |fbt|
51
53
  fbt.insert.foo = 42
52
54
  end
53
55
  )
54
56
  assert_equal(1, fb.size)
57
+ assert(log.to_s.include?('modified'), log)
58
+ end
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
+
69
+ def test_with_empty_txn
70
+ log = Loog::Buffer.new
71
+ fb = Factbase::Looged.new(Factbase.new, log)
72
+ assert(!fb.txn { |fbt| fbt.query('(always)').each.to_a })
73
+ assert(log.to_s.include?('didn\'t touch'), log)
55
74
  end
56
75
 
57
76
  def test_returns_int
@@ -102,7 +121,7 @@ class TestLooged < Minitest::Test
102
121
  'Set \'bar\' to 3 (Integer)',
103
122
  'Set \'str\' to "Он поскорей звонит. Вбегает\n ... Отъехать в поле к двум дубкам." (String)',
104
123
  'Found 1 fact(s) by \'(exists bar)\'',
105
- 'Deleted 3 fact(s) by \'(not (exists bar))\''
124
+ 'Deleted 3 fact(s) out of 4 by \'(not (exists bar))\''
106
125
  ].each do |s|
107
126
  assert(log.to_s.include?(s), "#{log}\n")
108
127
  end
@@ -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
@@ -72,6 +107,16 @@ class TestFactbase < Minitest::Test
72
107
  assert_equal(2, fb2.size)
73
108
  end
74
109
 
110
+ def test_txn_returns_boolean
111
+ fb = Factbase.new
112
+ assert(fb.txn { true }.is_a?(FalseClass))
113
+ assert(fb.txn(&:insert).is_a?(TrueClass))
114
+ assert(fb.txn { |fbt| fbt.insert.bar = 42 })
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 })
118
+ end
119
+
75
120
  def test_run_txn
76
121
  fb = Factbase.new
77
122
  fb.txn do |fbt|
@@ -90,6 +135,17 @@ class TestFactbase < Minitest::Test
90
135
  assert_equal(2, fb.size)
91
136
  end
92
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
+
93
149
  def test_run_txn_with_inv
94
150
  fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
95
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.45
4
+ version: 0.0.47
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-07 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