factbase 0.0.45 → 0.0.47

Sign up to get free protection for your applications and to get access to all the features.
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