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