factbase 0.5.0 → 0.5.2
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/Gemfile.lock +1 -1
- data/README.md +10 -10
- data/lib/factbase/syntax.rb +18 -8
- data/lib/factbase.rb +1 -1
- data/test/factbase/test_syntax.rb +23 -0
- data/test/test_factbase.rb +8 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4cc974ce82891e109f03740f7562f798e955da874dc2b1a91b0858bda7206b0
|
4
|
+
data.tar.gz: 7aac5d75a5e10fca5a6f74e9fc2e5b83ecaf9cc9d669e3384c52b508b732b45f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 436eb0bc06eabc25825b36fdf8293f2654219e42a0470512d42bc075670a25d51936006303a67a4c183b7208ef1a9c8a940ab227af838fc4b4f8ae8020be4c4e
|
7
|
+
data.tar.gz: e7ea774220cdef5e471cb66c666c3cea20ee0672991b1e0fa5797b60f31e5430c9a0e9b4a40298d68654d067b1c1c1c8318fff492ba0162217cdb9724af8133e
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -184,19 +184,19 @@ This is the result of the benchmark:
|
|
184
184
|
|
185
185
|
<!-- benchmark_begin -->
|
186
186
|
| Action | Seconds | Details |
|
187
|
-
| --- | --: |
|
188
|
-
| `fb.insert()` | 7.
|
189
|
-
| `(gt time '2024-03-23T03:21:43Z')` | 0.
|
190
|
-
| `(gt cost 50)` | 0.
|
191
|
-
| `(eq title 'Object Thinking 5000')` | 0.
|
192
|
-
| `(and (eq foo 42.998) (or (gt bar 200) (absent zzz)))` | 0.
|
187
|
+
| --- | --: | --- |
|
188
|
+
| `fb.insert()` | 7.874 | Inserted 100000 facts |
|
189
|
+
| `(gt time '2024-03-23T03:21:43Z')` | 0.072 | Found 100000 fact(s) |
|
190
|
+
| `(gt cost 50)` | 0.069 | Found 50030 fact(s) |
|
191
|
+
| `(eq title 'Object Thinking 5000')` | 0.051 | Found 1 fact(s) |
|
192
|
+
| `(and (eq foo 42.998) (or (gt bar 200) (absent zzz)))` | 0.059 | Found 2 fact(s) |
|
193
193
|
| `(eq id (agg (always) (max id)))` | 0.131 | Found 1 fact(s) |
|
194
|
-
| `(join "c<=cost,b<=bar" (eq id (agg (always) (max id))))` | 0.
|
195
|
-
| `.export()` + `.import()` |
|
194
|
+
| `(join "c<=cost,b<=bar" (eq id (agg (always) (max id))))` | 0.695 | Found 100000 fact(s) |
|
195
|
+
| `.export()` + `.import()` | 1.931 | 11407636 bytes |
|
196
196
|
|
197
197
|
The results were calculated in [this GHA job][benchmark-gha]
|
198
|
-
on 2025-01-27 at 16:
|
198
|
+
on 2025-01-27 at 16:51,
|
199
199
|
on Linux with 4 CPUs.
|
200
200
|
<!-- benchmark_end -->
|
201
201
|
|
202
|
-
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/
|
202
|
+
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/12994018323
|
data/lib/factbase/syntax.rb
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
|
+
require 'backtrace'
|
23
24
|
require 'time'
|
24
25
|
require_relative '../factbase'
|
25
26
|
require_relative 'fact'
|
@@ -45,12 +46,19 @@ require_relative 'term_once'
|
|
45
46
|
# License:: MIT
|
46
47
|
class Factbase::Syntax
|
47
48
|
# Ctor.
|
49
|
+
#
|
50
|
+
# The class provided as the +term+ argument must have a three-argument
|
51
|
+
# constructor, similar to the class +Factbase::Term+. Also, it must be
|
52
|
+
# a child of +Factbase::Term+.
|
53
|
+
#
|
48
54
|
# @param [Factbase] fb Factbase
|
49
55
|
# @param [String] query The query, for example "(eq id 42)"
|
50
56
|
# @param [Class] term The class to instantiate to make every term
|
51
57
|
def initialize(fb, query, term: Factbase::Term)
|
52
58
|
@fb = fb
|
53
59
|
@query = query
|
60
|
+
raise "Term must be a Class, while #{term.class.name} provided" unless term.is_a?(Class)
|
61
|
+
raise "The 'term' must be a child of Factbase::Term, while #{term.name} provided" unless term <= Factbase::Term
|
54
62
|
@term = term
|
55
63
|
end
|
56
64
|
|
@@ -64,7 +72,7 @@ class Factbase::Syntax
|
|
64
72
|
t
|
65
73
|
end
|
66
74
|
rescue StandardError => e
|
67
|
-
err = "#{e.message} (#{e
|
75
|
+
err = "#{e.message} (#{Backtrace.new(e)}) in \"#{@query}\""
|
68
76
|
err = "#{err}, tokens: #{@tokens}" unless @tokens.nil?
|
69
77
|
raise err
|
70
78
|
end
|
@@ -77,11 +85,11 @@ class Factbase::Syntax
|
|
77
85
|
@tokens ||= to_tokens
|
78
86
|
raise 'No tokens' if @tokens.empty?
|
79
87
|
@ast ||= to_ast(@tokens, 0)
|
80
|
-
raise
|
81
|
-
|
82
|
-
raise 'No terms found' if
|
83
|
-
raise
|
84
|
-
|
88
|
+
raise "Too many terms (#{@ast[1]} != #{@tokens.size})" if @ast[1] != @tokens.size
|
89
|
+
t = @ast[0]
|
90
|
+
raise 'No terms found in the AST' if t.nil?
|
91
|
+
raise "#{t.class.name} is not an instance of #{@term}, thus not a proper term" unless t.is_a?(@term)
|
92
|
+
t
|
85
93
|
end
|
86
94
|
|
87
95
|
# Reads the stream of tokens, starting at the +at+ position. If the
|
@@ -109,7 +117,9 @@ class Factbase::Syntax
|
|
109
117
|
operands << operand
|
110
118
|
break if tokens[at] == :close
|
111
119
|
end
|
112
|
-
|
120
|
+
t = @term.new(@fb, op, operands)
|
121
|
+
t = Factbase::TermOnce.new(t, @fb.cache) if t.instance_of?(Factbase::Term)
|
122
|
+
[t, at + 1]
|
113
123
|
end
|
114
124
|
|
115
125
|
# Turns a query into an array of tokens.
|
@@ -164,7 +174,7 @@ class Factbase::Syntax
|
|
164
174
|
elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
|
165
175
|
Time.parse(t)
|
166
176
|
else
|
167
|
-
raise "Wrong symbol format (#{t})" unless t.match?(/^[_a-z
|
177
|
+
raise "Wrong symbol format (#{t})" unless t.match?(/^([_a-z][a-zA-Z0-9_]*|\$[a-z]+)$/)
|
168
178
|
t.to_sym
|
169
179
|
end
|
170
180
|
end
|
data/lib/factbase.rb
CHANGED
@@ -81,7 +81,7 @@ require 'yaml'
|
|
81
81
|
# License:: MIT
|
82
82
|
class Factbase
|
83
83
|
# Current version of the gem (changed by .rultor.yml on every release)
|
84
|
-
VERSION = '0.5.
|
84
|
+
VERSION = '0.5.2'
|
85
85
|
|
86
86
|
# An exception that may be thrown in a transaction, to roll it back.
|
87
87
|
class Rollback < StandardError; end
|
@@ -114,6 +114,7 @@ class TestSyntax < Minitest::Test
|
|
114
114
|
'',
|
115
115
|
'()',
|
116
116
|
'(foo',
|
117
|
+
'(foo $)',
|
117
118
|
'(foo 1) (bar 2)',
|
118
119
|
'some text',
|
119
120
|
'"hello, world!',
|
@@ -144,4 +145,26 @@ class TestSyntax < Minitest::Test
|
|
144
145
|
assert_equal(t, Factbase::Syntax.new(Factbase.new, s).to_term.to_s)
|
145
146
|
end
|
146
147
|
end
|
148
|
+
|
149
|
+
def test_fails_when_term_is_not_a_class
|
150
|
+
assert_raises(StandardError) { Factbase::Syntax.new(Factbase.new, '(foo 1)', term: 'hello') }
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_fails_when_term_is_wrong_class
|
154
|
+
assert_raises(StandardError) { Factbase::Syntax.new(Factbase.new, '(bar 1)', term: String).to_term }
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_fails_when_term_is_incorrectly_defined_class
|
158
|
+
assert_includes(
|
159
|
+
assert_raises(StandardError) { Factbase::Syntax.new(Factbase.new, '(bar 1)', term: FakeTerm).to_term }.message,
|
160
|
+
'wrong number of arguments'
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
class FakeTerm < Factbase::Term
|
165
|
+
def initialize(invalid)
|
166
|
+
super
|
167
|
+
@x = invalid
|
168
|
+
end
|
169
|
+
end
|
147
170
|
end
|
data/test/test_factbase.rb
CHANGED
@@ -294,20 +294,21 @@ class TestFactbase < Minitest::Test
|
|
294
294
|
# See details here https://github.com/yegor256/factbase/actions/runs/10492255419/job/29068637032
|
295
295
|
def test_concurrent_transactions_inserts
|
296
296
|
skip('Does not work')
|
297
|
+
total = 100
|
297
298
|
fb = Factbase.new
|
298
|
-
Threads.new(
|
299
|
+
Threads.new(total).assert do |i|
|
299
300
|
fb.txn do |fbt|
|
300
301
|
fact = fbt.insert
|
301
302
|
fact.thread_id = i
|
302
303
|
end
|
303
304
|
end
|
304
|
-
assert_equal(
|
305
|
-
assert_equal(
|
305
|
+
assert_equal(total, fb.size)
|
306
|
+
assert_equal(total, fb.query('(exists thread_id)').each.to_a.size)
|
306
307
|
end
|
307
308
|
|
308
309
|
def test_concurrent_transactions_with_rollbacks
|
309
310
|
fb = Factbase.new
|
310
|
-
Threads.new
|
311
|
+
Threads.new.assert do |i|
|
311
312
|
fb.txn do |fbt|
|
312
313
|
fact = fbt.insert
|
313
314
|
fact.thread_id = i
|
@@ -319,7 +320,8 @@ class TestFactbase < Minitest::Test
|
|
319
320
|
|
320
321
|
def test_concurrent_transactions_successful
|
321
322
|
fb = Factbase.new
|
322
|
-
|
323
|
+
total = 100
|
324
|
+
Threads.new(total).assert do |i|
|
323
325
|
fb.txn do |fbt|
|
324
326
|
fact = fbt.insert
|
325
327
|
fact.thread_id = i
|
@@ -327,7 +329,7 @@ class TestFactbase < Minitest::Test
|
|
327
329
|
end
|
328
330
|
end
|
329
331
|
facts = fb.query('(exists thread_id)').each.to_a
|
330
|
-
assert_equal(
|
332
|
+
assert_equal(total, facts.size)
|
331
333
|
facts.each do |fact|
|
332
334
|
assert_equal(fact.value, fact.thread_id * 10)
|
333
335
|
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.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: backtrace
|