factbase 0.0.46 → 0.0.48

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: 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