factbase 0.4.0 → 0.5.1
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 +4 -4
- data/.0pdd.yml +1 -1
- data/.github/workflows/actionlint.yml +1 -1
- data/.github/workflows/benchmark.yml +64 -0
- data/.github/workflows/codecov.yml +4 -2
- data/.github/workflows/copyrights.yml +2 -2
- data/.github/workflows/markdown-lint.yml +1 -1
- data/.github/workflows/pdd.yml +1 -1
- data/.github/workflows/rake.yml +2 -2
- data/.github/workflows/xcop.yml +1 -1
- data/.github/workflows/yamllint.yml +1 -1
- data/.gitignore +1 -1
- data/.rubocop.yml +6 -1
- data/.rultor.yml +2 -2
- data/.simplecov +1 -1
- data/.yamllint.yml +9 -4
- data/Gemfile +9 -7
- data/Gemfile.lock +98 -78
- data/LICENSE.txt +1 -1
- data/README.md +33 -0
- data/Rakefile +6 -1
- data/benchmarks/simple.rb +96 -0
- data/factbase.gemspec +1 -1
- data/lib/factbase/accum.rb +2 -2
- data/lib/factbase/fact.rb +6 -4
- data/lib/factbase/flatten.rb +2 -2
- data/lib/factbase/inv.rb +2 -2
- data/lib/factbase/looged.rb +6 -5
- data/lib/factbase/pre.rb +2 -2
- data/lib/factbase/query.rb +16 -8
- data/lib/factbase/query_once.rb +71 -0
- data/lib/factbase/rules.rb +3 -3
- data/lib/factbase/syntax.rb +21 -11
- data/lib/factbase/tee.rb +2 -2
- data/lib/factbase/term.rb +33 -5
- data/lib/factbase/term_once.rb +84 -0
- data/lib/factbase/terms/aggregates.rb +4 -4
- data/lib/factbase/terms/aliases.rb +5 -5
- data/lib/factbase/terms/casting.rb +2 -2
- data/lib/factbase/terms/debug.rb +2 -2
- data/lib/factbase/terms/defn.rb +2 -2
- data/lib/factbase/terms/logical.rb +3 -3
- data/lib/factbase/terms/math.rb +2 -2
- data/lib/factbase/terms/meta.rb +2 -2
- data/lib/factbase/terms/ordering.rb +2 -2
- data/lib/factbase/terms/strings.rb +2 -2
- data/lib/factbase/terms/system.rb +2 -2
- data/lib/factbase/to_json.rb +2 -2
- data/lib/factbase/to_xml.rb +2 -2
- data/lib/factbase/to_yaml.rb +2 -2
- data/lib/factbase.rb +16 -6
- data/test/factbase/terms/test_aggregates.rb +6 -6
- data/test/factbase/terms/test_aliases.rb +6 -6
- data/test/factbase/terms/test_casting.rb +6 -6
- data/test/factbase/terms/test_debug.rb +53 -0
- data/test/factbase/terms/test_defn.rb +15 -15
- data/test/factbase/terms/test_logical.rb +15 -13
- data/test/factbase/terms/test_math.rb +42 -42
- data/test/factbase/terms/test_meta.rb +87 -0
- data/test/factbase/terms/test_ordering.rb +45 -0
- data/test/factbase/terms/test_strings.rb +8 -8
- data/test/factbase/terms/test_system.rb +6 -6
- data/test/factbase/test_accum.rb +9 -9
- data/test/factbase/test_fact.rb +22 -22
- data/test/factbase/test_flatten.rb +6 -6
- data/test/factbase/test_inv.rb +3 -3
- data/test/factbase/test_looged.rb +13 -13
- data/test/factbase/test_pre.rb +2 -2
- data/test/factbase/test_query.rb +17 -17
- data/test/factbase/test_rules.rb +8 -8
- data/test/factbase/test_syntax.rb +30 -9
- data/test/factbase/test_tee.rb +11 -11
- data/test/factbase/test_term.rb +18 -18
- data/test/factbase/test_to_json.rb +4 -4
- data/test/factbase/test_to_xml.rb +12 -14
- data/test/factbase/test_to_yaml.rb +3 -3
- data/test/test__helper.rb +2 -2
- data/test/test_factbase.rb +200 -18
- metadata +12 -5
data/test/factbase/test_term.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
3
|
+
# Copyright (c) 2024-2025 Yegor Bugayenko
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -25,34 +25,34 @@ require_relative '../../lib/factbase/term'
|
|
25
25
|
|
26
26
|
# Term test.
|
27
27
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
-
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
28
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
29
29
|
# License:: MIT
|
30
30
|
class TestTerm < Minitest::Test
|
31
31
|
def test_false_matching
|
32
|
-
t = Factbase::Term.new(:never, [])
|
33
|
-
|
32
|
+
t = Factbase::Term.new(Factbase.new, :never, [])
|
33
|
+
refute(t.evaluate(fact('foo' => [100]), []))
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_size_matching
|
37
|
-
t = Factbase::Term.new(:size, [:foo])
|
37
|
+
t = Factbase::Term.new(Factbase.new, :size, [:foo])
|
38
38
|
assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90]), []))
|
39
39
|
assert_equal(0, t.evaluate(fact('bar' => 100), []))
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_exists_matching
|
43
|
-
t = Factbase::Term.new(:exists, [:foo])
|
43
|
+
t = Factbase::Term.new(Factbase.new, :exists, [:foo])
|
44
44
|
assert(t.evaluate(fact('foo' => [42, 12, -90]), []))
|
45
|
-
|
45
|
+
refute(t.evaluate(fact('bar' => 100), []))
|
46
46
|
end
|
47
47
|
|
48
48
|
def test_absent_matching
|
49
|
-
t = Factbase::Term.new(:absent, [:foo])
|
49
|
+
t = Factbase::Term.new(Factbase.new, :absent, [:foo])
|
50
50
|
assert(t.evaluate(fact('z' => [42, 12, -90]), []))
|
51
|
-
|
51
|
+
refute(t.evaluate(fact('foo' => 100), []))
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_type_matching
|
55
|
-
t = Factbase::Term.new(:type, [:foo])
|
55
|
+
t = Factbase::Term.new(Factbase.new, :type, [:foo])
|
56
56
|
assert_equal('Integer', t.evaluate(fact('foo' => 42), []))
|
57
57
|
assert_equal('Integer', t.evaluate(fact('foo' => [42]), []))
|
58
58
|
assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3]), []))
|
@@ -64,30 +64,30 @@ class TestTerm < Minitest::Test
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def test_past
|
67
|
-
t = Factbase::Term.new(:prev, [:foo])
|
67
|
+
t = Factbase::Term.new(Factbase.new, :prev, [:foo])
|
68
68
|
assert_nil(t.evaluate(fact('foo' => 4), []))
|
69
69
|
assert_equal([4], t.evaluate(fact('foo' => 5), []))
|
70
70
|
end
|
71
71
|
|
72
72
|
def test_at
|
73
|
-
t = Factbase::Term.new(:at, [1, :foo])
|
73
|
+
t = Factbase::Term.new(Factbase.new, :at, [1, :foo])
|
74
74
|
assert_nil(t.evaluate(fact('foo' => 4), []))
|
75
75
|
assert_equal(5, t.evaluate(fact('foo' => [4, 5]), []))
|
76
76
|
end
|
77
77
|
|
78
78
|
def test_report_missing_term
|
79
|
-
t = Factbase::Term.new(:something, [])
|
80
|
-
msg = assert_raises do
|
79
|
+
t = Factbase::Term.new(Factbase.new, :something, [])
|
80
|
+
msg = assert_raises(StandardError) do
|
81
81
|
t.evaluate(fact, [])
|
82
82
|
end.message
|
83
|
-
|
83
|
+
assert_includes(msg, 'not defined at (something)', msg)
|
84
84
|
end
|
85
85
|
|
86
86
|
def test_report_other_error
|
87
|
-
t = Factbase::Term.new(:at, [])
|
88
|
-
msg = assert_raises do
|
87
|
+
t = Factbase::Term.new(Factbase.new, :at, [])
|
88
|
+
msg = assert_raises(StandardError) do
|
89
89
|
t.evaluate(fact, [])
|
90
90
|
end.message
|
91
|
-
|
91
|
+
assert_includes(msg, 'at (at)', msg)
|
92
92
|
end
|
93
93
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
3
|
+
# Copyright (c) 2024-2025 Yegor Bugayenko
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -27,7 +27,7 @@ require_relative '../../lib/factbase/to_json'
|
|
27
27
|
|
28
28
|
# Test.
|
29
29
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
-
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
31
31
|
# License:: MIT
|
32
32
|
class TestToJSON < Minitest::Test
|
33
33
|
def test_simple_rendering
|
@@ -37,7 +37,7 @@ class TestToJSON < Minitest::Test
|
|
37
37
|
f.foo = 256
|
38
38
|
to = Factbase::ToJSON.new(fb)
|
39
39
|
json = JSON.parse(to.json)
|
40
|
-
|
40
|
+
assert_equal(256, json[0]['foo'][1])
|
41
41
|
end
|
42
42
|
|
43
43
|
def test_sort_keys
|
@@ -47,6 +47,6 @@ class TestToJSON < Minitest::Test
|
|
47
47
|
f.b = 1
|
48
48
|
f.a = 256
|
49
49
|
json = Factbase::ToJSON.new(fb).json
|
50
|
-
|
50
|
+
assert_includes(json, '{"a":256,"b":1,"c":42}', json)
|
51
51
|
end
|
52
52
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
3
|
+
# Copyright (c) 2024-2025 Yegor Bugayenko
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -27,7 +27,7 @@ require_relative '../../lib/factbase/to_xml'
|
|
27
27
|
|
28
28
|
# Test.
|
29
29
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
-
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
31
31
|
# License:: MIT
|
32
32
|
class TestToXML < Minitest::Test
|
33
33
|
def test_simple_rendering
|
@@ -35,11 +35,9 @@ class TestToXML < Minitest::Test
|
|
35
35
|
fb.insert.t = Time.now
|
36
36
|
to = Factbase::ToXML.new(fb)
|
37
37
|
xml = Nokogiri::XML.parse(to.xml)
|
38
|
-
|
39
|
-
|
40
|
-
xml.xpath('/fb/f/t/text()').text
|
41
|
-
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/
|
42
|
-
)
|
38
|
+
refute_empty(xml.xpath('/fb/f[t]'))
|
39
|
+
assert_match(
|
40
|
+
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, xml.xpath('/fb/f/t/text()').text
|
43
41
|
)
|
44
42
|
end
|
45
43
|
|
@@ -48,7 +46,7 @@ class TestToXML < Minitest::Test
|
|
48
46
|
fb.insert.t = "\uffff < > & ' \""
|
49
47
|
to = Factbase::ToXML.new(fb)
|
50
48
|
xml = Nokogiri::XML.parse(to.xml)
|
51
|
-
|
49
|
+
refute_empty(xml.xpath('/fb/f[t]'))
|
52
50
|
end
|
53
51
|
|
54
52
|
def test_meta_data_presence
|
@@ -56,8 +54,8 @@ class TestToXML < Minitest::Test
|
|
56
54
|
fb.insert.x = 42
|
57
55
|
to = Factbase::ToXML.new(fb)
|
58
56
|
xml = Nokogiri::XML.parse(to.xml)
|
59
|
-
|
60
|
-
|
57
|
+
refute_empty(xml.xpath('/fb[@version]'))
|
58
|
+
refute_empty(xml.xpath('/fb[@size]'))
|
61
59
|
end
|
62
60
|
|
63
61
|
def test_to_xml_with_short_names
|
@@ -67,8 +65,8 @@ class TestToXML < Minitest::Test
|
|
67
65
|
f.f = 2
|
68
66
|
to = Factbase::ToXML.new(fb)
|
69
67
|
xml = Nokogiri::XML.parse(to.xml)
|
70
|
-
|
71
|
-
|
68
|
+
refute_empty(xml.xpath('/fb/f/type'))
|
69
|
+
refute_empty(xml.xpath('/fb/f/f'))
|
72
70
|
end
|
73
71
|
|
74
72
|
def test_show_types_as_attributes
|
@@ -89,7 +87,7 @@ class TestToXML < Minitest::Test
|
|
89
87
|
'/fb/f/d[@t="T"]',
|
90
88
|
'/fb/f/e/v[@t="S"]',
|
91
89
|
'/fb/f/e/v[@t="I"]'
|
92
|
-
].each { |x|
|
90
|
+
].each { |x| refute_empty(xml.xpath(x), out) }
|
93
91
|
end
|
94
92
|
|
95
93
|
def test_sorts_keys
|
@@ -102,7 +100,7 @@ class TestToXML < Minitest::Test
|
|
102
100
|
to = Factbase::ToXML.new(fb)
|
103
101
|
xml = Nokogiri::XML.parse(to.xml)
|
104
102
|
%w[a c t x].each_with_index do |e, i|
|
105
|
-
|
103
|
+
refute_empty(xml.xpath("/fb/f/#{e}[count(preceding-sibling::*) = #{i}]"), e)
|
106
104
|
end
|
107
105
|
end
|
108
106
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
3
|
+
# Copyright (c) 2024-2025 Yegor Bugayenko
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -27,7 +27,7 @@ require_relative '../../lib/factbase/to_yaml'
|
|
27
27
|
|
28
28
|
# Test.
|
29
29
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
-
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
31
31
|
# License:: MIT
|
32
32
|
class TestToYAML < Minitest::Test
|
33
33
|
def test_simple_rendering
|
@@ -51,6 +51,6 @@ class TestToYAML < Minitest::Test
|
|
51
51
|
f.a = 256
|
52
52
|
f.c = 10
|
53
53
|
yaml = Factbase::ToYAML.new(fb).yaml
|
54
|
-
|
54
|
+
assert_includes(yaml, "a: 256\n b: 42\n c: 10", yaml)
|
55
55
|
end
|
56
56
|
end
|
data/test/test__helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
3
|
+
# Copyright (c) 2024-2025 Yegor Bugayenko
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -37,6 +37,6 @@ Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
|
37
37
|
class Minitest::Test
|
38
38
|
def fact(map = {})
|
39
39
|
require 'factbase/fact'
|
40
|
-
Factbase::Fact.new(Mutex.new, map)
|
40
|
+
Factbase::Fact.new(Factbase.new, Mutex.new, map)
|
41
41
|
end
|
42
42
|
end
|
data/test/test_factbase.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
3
|
+
# Copyright (c) 2024-2025 Yegor Bugayenko
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -22,6 +22,7 @@
|
|
22
22
|
|
23
23
|
require 'minitest/autorun'
|
24
24
|
require 'loog'
|
25
|
+
require 'threads'
|
25
26
|
require_relative '../lib/factbase'
|
26
27
|
require_relative '../lib/factbase/rules'
|
27
28
|
require_relative '../lib/factbase/inv'
|
@@ -30,7 +31,7 @@ require_relative '../lib/factbase/looged'
|
|
30
31
|
|
31
32
|
# Factbase main module test.
|
32
33
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
33
|
-
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
34
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
34
35
|
# License:: MIT
|
35
36
|
class TestFactbase < Minitest::Test
|
36
37
|
def test_injects_data_correctly
|
@@ -48,13 +49,22 @@ class TestFactbase < Minitest::Test
|
|
48
49
|
assert_equal([2, 3], maps[1]['bar'])
|
49
50
|
end
|
50
51
|
|
52
|
+
def test_query_many_times
|
53
|
+
fb = Factbase.new
|
54
|
+
total = 5
|
55
|
+
total.times { fb.insert }
|
56
|
+
total.times do
|
57
|
+
assert_equal(5, fb.query('(always)').each.to_a.size)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
51
61
|
def test_simple_setting
|
52
62
|
fb = Factbase.new
|
53
63
|
fb.insert
|
54
64
|
fb.insert.bar = 88
|
55
65
|
found = 0
|
56
66
|
fb.query('(exists bar)').each do |f|
|
57
|
-
|
67
|
+
assert_predicate(f.bar, :positive?)
|
58
68
|
f.foo = 42
|
59
69
|
assert_equal(42, f.foo)
|
60
70
|
found += 1
|
@@ -87,17 +97,17 @@ class TestFactbase < Minitest::Test
|
|
87
97
|
File.binwrite(f.path, f1.export)
|
88
98
|
f2.import(File.binread(f.path))
|
89
99
|
end
|
90
|
-
assert_equal(1, f2.query('(eq foo 42)').each.to_a.
|
100
|
+
assert_equal(1, f2.query('(eq foo 42)').each.to_a.size)
|
91
101
|
end
|
92
102
|
|
93
103
|
def test_reads_from_empty_file
|
94
104
|
fb = Factbase.new
|
95
105
|
Tempfile.open do |f|
|
96
106
|
File.binwrite(f.path, '')
|
97
|
-
|
98
|
-
assert_raises do
|
107
|
+
assert_includes(
|
108
|
+
assert_raises(StandardError) do
|
99
109
|
fb.import(File.binread(f.path))
|
100
|
-
end.message
|
110
|
+
end.message, 'cannot load a factbase'
|
101
111
|
)
|
102
112
|
end
|
103
113
|
end
|
@@ -121,10 +131,10 @@ class TestFactbase < Minitest::Test
|
|
121
131
|
|
122
132
|
def test_txn_returns_boolean
|
123
133
|
fb = Factbase.new
|
124
|
-
|
125
|
-
|
134
|
+
assert_kind_of(FalseClass, fb.txn { true })
|
135
|
+
assert_kind_of(TrueClass, fb.txn(&:insert))
|
126
136
|
assert(fb.txn { |fbt| fbt.insert.bar = 42 })
|
127
|
-
|
137
|
+
refute(fb.txn { |fbt| fbt.query('(always)').each.to_a })
|
128
138
|
assert(fb.txn { |fbt| fbt.query('(always)').each { |f| f.hello = 33 } })
|
129
139
|
assert(fb.txn { |fbt| fbt.query('(always)').each.to_a[0].zzz = 33 })
|
130
140
|
end
|
@@ -136,13 +146,13 @@ class TestFactbase < Minitest::Test
|
|
136
146
|
fbt.insert.z = 42
|
137
147
|
end
|
138
148
|
assert_equal(2, fb.size)
|
139
|
-
|
140
|
-
assert_raises do
|
149
|
+
assert_includes(
|
150
|
+
assert_raises(StandardError) do
|
141
151
|
fb.txn do |fbt|
|
142
152
|
fbt.insert.foo = 42
|
143
153
|
throw 'intentionally'
|
144
154
|
end
|
145
|
-
end.message
|
155
|
+
end.message, 'intentionally'
|
146
156
|
)
|
147
157
|
assert_equal(2, fb.size)
|
148
158
|
end
|
@@ -163,12 +173,12 @@ class TestFactbase < Minitest::Test
|
|
163
173
|
fb.insert.bar = 3
|
164
174
|
fb.insert.foo = 5
|
165
175
|
assert_equal(2, fb.size)
|
166
|
-
|
167
|
-
assert_raises do
|
176
|
+
assert_includes(
|
177
|
+
assert_raises(StandardError) do
|
168
178
|
fb.txn do |fbt|
|
169
179
|
fbt.insert.foo = 42
|
170
180
|
end
|
171
|
-
end.message
|
181
|
+
end.message, 'oops'
|
172
182
|
)
|
173
183
|
assert_equal(2, fb.size)
|
174
184
|
end
|
@@ -185,7 +195,7 @@ class TestFactbase < Minitest::Test
|
|
185
195
|
d.txn do |fbt|
|
186
196
|
fbt.insert.bar = 455
|
187
197
|
end
|
188
|
-
assert_raises do
|
198
|
+
assert_raises(StandardError) do
|
189
199
|
d.txn do |fbt|
|
190
200
|
fbt.insert
|
191
201
|
throw 'oops'
|
@@ -216,7 +226,179 @@ class TestFactbase < Minitest::Test
|
|
216
226
|
fbt.insert.bar = 33
|
217
227
|
raise Factbase::Rollback
|
218
228
|
end
|
219
|
-
|
229
|
+
refute(modified)
|
220
230
|
assert_equal(0, fb.query('(always)').each.to_a.size)
|
221
231
|
end
|
232
|
+
|
233
|
+
def test_concurrent_inserts
|
234
|
+
fb = Factbase.new
|
235
|
+
Threads.new(100).assert do
|
236
|
+
fact = fb.insert
|
237
|
+
fact.foo = 42
|
238
|
+
fact.bar = 49
|
239
|
+
fact.value = fact.foo * fact.bar
|
240
|
+
end
|
241
|
+
assert_equal(100, fb.size)
|
242
|
+
assert_equal(100, fb.query('(eq foo 42)').each.to_a.size)
|
243
|
+
assert_equal(100, fb.query('(eq bar 49)').each.to_a.size)
|
244
|
+
assert_equal(100, fb.query("(eq value #{42 * 49})").each.to_a.size)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_different_values_when_concurrent_inserts
|
248
|
+
fb = Factbase.new
|
249
|
+
Threads.new(100).assert do |i|
|
250
|
+
fb.insert.foo = i
|
251
|
+
end
|
252
|
+
assert_equal(100, fb.size)
|
253
|
+
Threads.new(100) do |i|
|
254
|
+
f = fb.query("(eq foo #{i})").each.to_a
|
255
|
+
assert_equal(1, f.count)
|
256
|
+
assert_equal(i, f.first.foo)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# @todo #98:1h I assumed that the test `test_different_properties_when_concurrent_inserts` would be passed.
|
261
|
+
# I see like this:
|
262
|
+
# ```
|
263
|
+
# [2024-08-22 21:14:53.962] ERROR -- Expected: 1
|
264
|
+
# Actual: 0: nil
|
265
|
+
# [2024-08-22 21:14:53.962] ERROR -- Expected: 1
|
266
|
+
# Actual: 0: nil
|
267
|
+
# test_different_properties_when_concurrent_inserts ERROR (0.01s)
|
268
|
+
# Minitest::UnexpectedError: RuntimeError: Only 0 out of 5 threads completed successfully
|
269
|
+
# /home/suban/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/threads-0.4.0/lib/threads.rb:73:in `assert'
|
270
|
+
# test/test_factbase.rb:265:in `test_different_properties_when_concurrent_inserts'
|
271
|
+
# ```
|
272
|
+
def test_different_properties_when_concurrent_inserts
|
273
|
+
skip('Does not work')
|
274
|
+
fb = Factbase.new
|
275
|
+
Threads.new(5).assert do |i|
|
276
|
+
fb.insert.send(:"prop_#{i}=", i)
|
277
|
+
end
|
278
|
+
assert_equal(5, fb.size)
|
279
|
+
Threads.new(5).assert do |i|
|
280
|
+
prop = "prop_#{i}"
|
281
|
+
f = fb.query("(eq #{prop} #{i})").each.to_a
|
282
|
+
assert_equal(1, f.count)
|
283
|
+
assert_equal(i, f.first.send(prop.to_sym))
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# @todo #98:1h I assumed that the test `test_concurrent_transactions_inserts` would be passed.
|
288
|
+
# I see like this:
|
289
|
+
# ```
|
290
|
+
# Expected: 100
|
291
|
+
# Actual: 99
|
292
|
+
# D:/a/factbase/factbase/test/test_factbase.rb:281:in `test_concurrent_transactions_inserts'
|
293
|
+
# ```
|
294
|
+
# See details here https://github.com/yegor256/factbase/actions/runs/10492255419/job/29068637032
|
295
|
+
def test_concurrent_transactions_inserts
|
296
|
+
skip('Does not work')
|
297
|
+
total = 100
|
298
|
+
fb = Factbase.new
|
299
|
+
Threads.new(total).assert do |i|
|
300
|
+
fb.txn do |fbt|
|
301
|
+
fact = fbt.insert
|
302
|
+
fact.thread_id = i
|
303
|
+
end
|
304
|
+
end
|
305
|
+
assert_equal(total, fb.size)
|
306
|
+
assert_equal(total, fb.query('(exists thread_id)').each.to_a.size)
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_concurrent_transactions_with_rollbacks
|
310
|
+
fb = Factbase.new
|
311
|
+
Threads.new.assert do |i|
|
312
|
+
fb.txn do |fbt|
|
313
|
+
fact = fbt.insert
|
314
|
+
fact.thread_id = i
|
315
|
+
raise Factbase::Rollback
|
316
|
+
end
|
317
|
+
end
|
318
|
+
assert_equal(0, fb.size)
|
319
|
+
end
|
320
|
+
|
321
|
+
def test_concurrent_transactions_successful
|
322
|
+
fb = Factbase.new
|
323
|
+
total = 100
|
324
|
+
Threads.new(total).assert do |i|
|
325
|
+
fb.txn do |fbt|
|
326
|
+
fact = fbt.insert
|
327
|
+
fact.thread_id = i
|
328
|
+
fact.value = i * 10
|
329
|
+
end
|
330
|
+
end
|
331
|
+
facts = fb.query('(exists thread_id)').each.to_a
|
332
|
+
assert_equal(total, facts.size)
|
333
|
+
facts.each do |fact|
|
334
|
+
assert_equal(fact.value, fact.thread_id * 10)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# @todo #98:1h I assumed that the test `test_concurrent_queries` would be passed.
|
339
|
+
# I see like this:
|
340
|
+
# ```
|
341
|
+
# [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
|
342
|
+
# Actual: [0, 0]: nil
|
343
|
+
# [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
|
344
|
+
# Actual: [0, 0]: nil
|
345
|
+
# test_concurrent_queries ERROR (0.00s)
|
346
|
+
# Minitest::UnexpectedError: RuntimeError: Only 0 out of 2 threads completed successfully
|
347
|
+
# /home/suban/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/threads-0.4.0/lib/threads.rb:73:in `assert'
|
348
|
+
# test/test_factbase.rb:329:in `test_concurrent_queries'
|
349
|
+
# ```
|
350
|
+
def test_concurrent_queries
|
351
|
+
skip('Does not work')
|
352
|
+
fb = Factbase.new
|
353
|
+
Threads.new(2).assert do |i|
|
354
|
+
fact = fb.insert
|
355
|
+
fact.thread_id = i
|
356
|
+
fact.value = i * 10
|
357
|
+
end
|
358
|
+
Threads.new(2).assert do
|
359
|
+
results = fb.query('(exists thread_id)').each.to_a
|
360
|
+
assert_equal(2, results.size)
|
361
|
+
|
362
|
+
thread_ids = results.map(&:thread_id)
|
363
|
+
assert_equal((0..1).to_a, thread_ids.sort)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def test_export_import_concurrent
|
368
|
+
fb = Factbase.new
|
369
|
+
Threads.new(100).assert do
|
370
|
+
fact = fb.insert
|
371
|
+
fact.value = 42
|
372
|
+
end
|
373
|
+
Threads.new(5).assert do
|
374
|
+
new_fb = Factbase.new
|
375
|
+
new_fb.import(fb.export)
|
376
|
+
assert_equal(fb.size, new_fb.size)
|
377
|
+
facts = fb.query('(eq value 42)').each.to_a
|
378
|
+
assert_equal(100, facts.size)
|
379
|
+
facts.each do |fact|
|
380
|
+
new_fact = new_fb.query("(eq value #{fact.value})").each.to_a.first
|
381
|
+
assert_equal(fact.value, new_fact.value)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def test_dup_concurrent
|
387
|
+
fb = Factbase.new
|
388
|
+
mutex = Mutex.new
|
389
|
+
Threads.new(100).assert do
|
390
|
+
fact = fb.insert
|
391
|
+
fact.foo = 42
|
392
|
+
end
|
393
|
+
fbs = []
|
394
|
+
Threads.new(100).assert do
|
395
|
+
mutex.synchronize do
|
396
|
+
fbs << fb.dup
|
397
|
+
end
|
398
|
+
end
|
399
|
+
assert_equal(100, fbs.size)
|
400
|
+
fbs.each do |factbase|
|
401
|
+
assert_equal(100, factbase.query('(eq foo 42)').each.to_a.size)
|
402
|
+
end
|
403
|
+
end
|
222
404
|
end
|
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.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: backtrace
|
@@ -137,6 +137,7 @@ files:
|
|
137
137
|
- ".0pdd.yml"
|
138
138
|
- ".gitattributes"
|
139
139
|
- ".github/workflows/actionlint.yml"
|
140
|
+
- ".github/workflows/benchmark.yml"
|
140
141
|
- ".github/workflows/codecov.yml"
|
141
142
|
- ".github/workflows/copyrights.yml"
|
142
143
|
- ".github/workflows/markdown-lint.yml"
|
@@ -155,6 +156,7 @@ files:
|
|
155
156
|
- LICENSE.txt
|
156
157
|
- README.md
|
157
158
|
- Rakefile
|
159
|
+
- benchmarks/simple.rb
|
158
160
|
- factbase.gemspec
|
159
161
|
- lib/factbase.rb
|
160
162
|
- lib/factbase/accum.rb
|
@@ -164,10 +166,12 @@ files:
|
|
164
166
|
- lib/factbase/looged.rb
|
165
167
|
- lib/factbase/pre.rb
|
166
168
|
- lib/factbase/query.rb
|
169
|
+
- lib/factbase/query_once.rb
|
167
170
|
- lib/factbase/rules.rb
|
168
171
|
- lib/factbase/syntax.rb
|
169
172
|
- lib/factbase/tee.rb
|
170
173
|
- lib/factbase/term.rb
|
174
|
+
- lib/factbase/term_once.rb
|
171
175
|
- lib/factbase/terms/aggregates.rb
|
172
176
|
- lib/factbase/terms/aliases.rb
|
173
177
|
- lib/factbase/terms/casting.rb
|
@@ -186,9 +190,12 @@ files:
|
|
186
190
|
- test/factbase/terms/test_aggregates.rb
|
187
191
|
- test/factbase/terms/test_aliases.rb
|
188
192
|
- test/factbase/terms/test_casting.rb
|
193
|
+
- test/factbase/terms/test_debug.rb
|
189
194
|
- test/factbase/terms/test_defn.rb
|
190
195
|
- test/factbase/terms/test_logical.rb
|
191
196
|
- test/factbase/terms/test_math.rb
|
197
|
+
- test/factbase/terms/test_meta.rb
|
198
|
+
- test/factbase/terms/test_ordering.rb
|
192
199
|
- test/factbase/terms/test_strings.rb
|
193
200
|
- test/factbase/terms/test_system.rb
|
194
201
|
- test/factbase/test_accum.rb
|
@@ -212,7 +219,7 @@ licenses:
|
|
212
219
|
- MIT
|
213
220
|
metadata:
|
214
221
|
rubygems_mfa_required: 'true'
|
215
|
-
post_install_message:
|
222
|
+
post_install_message:
|
216
223
|
rdoc_options:
|
217
224
|
- "--charset=UTF-8"
|
218
225
|
require_paths:
|
@@ -229,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
229
236
|
version: '0'
|
230
237
|
requirements: []
|
231
238
|
rubygems_version: 3.4.10
|
232
|
-
signing_key:
|
239
|
+
signing_key:
|
233
240
|
specification_version: 4
|
234
241
|
summary: Factbase
|
235
242
|
test_files: []
|