factbase 0.13.0 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb06b280801b081a1ae2c02147b4c960017c52a0cdb2c38dcd895cff53943344
4
- data.tar.gz: cde4f73ddf30a7d4b6d9f9b8dadf823c948f6bb5fa672ad180867a46ef7b9ce2
3
+ metadata.gz: 0dd01490add2a273a8eb479d1cbbb13ab7489f08e2654de8e4d2ce6b141ff9c7
4
+ data.tar.gz: d04b49fb4e9a0caa991143c5d7830d6d2a240a1753090f2c6ac52219e1460e2a
5
5
  SHA512:
6
- metadata.gz: 42072d2fd692798e3dd25a16167416cc6afd601d12998a1d0a5d79b2ab18d66bc09fcf40ae11e2a53af4df6fd9f5cba56a6adf32b94edd11e5122c9992304bbc
7
- data.tar.gz: e6ec47e6c0715ac9c8148bc5ac25e36f6e009dd7da34c32386a7b5d97dcdf11c6ed6e5ed17f27ca03e2ad454bf5dacae0624ec7ab5119a4396f4a4134a6f5b11
6
+ metadata.gz: e7979d0c8e440a3f93891182d50d069b84fdbf403e080dfcc55999d9f12e848554c8282533214e56ad34a9467a8ac9d6700d64e08dff876c70fa6d521afb4f00
7
+ data.tar.gz: 6a9d7711e79ed74ae956f29d13de51e4c6715910c394c273636c875acaf5526223b1813a2d780f977f4049d19e6b734b5cf8832178b69a1f264b3e38888244bd
data/Gemfile CHANGED
@@ -11,12 +11,12 @@ gem 'minitest-reporters', '~>1.7', require: false
11
11
  gem 'os', '~>1.1', require: false
12
12
  gem 'qbash', '~>0.4', require: false
13
13
  gem 'rake', '~>13.2', require: false
14
- gem 'rdoc', '6.14.1', require: false # GPL
14
+ gem 'rdoc', '6.14.2', require: false # GPL
15
15
  gem 'rubocop', '~>1.74', require: false
16
16
  gem 'rubocop-minitest', '~>0.38', require: false
17
17
  gem 'rubocop-performance', '~>1.25', require: false
18
18
  gem 'rubocop-rake', '~>0.7', require: false
19
19
  gem 'simplecov', '~>0.22', require: false
20
- gem 'simplecov-cobertura', '~>2.1', require: false
20
+ gem 'simplecov-cobertura', '~>3.0', require: false
21
21
  gem 'threads', '~>0.4', require: false
22
22
  gem 'yard', '~>0.9', require: false
data/Gemfile.lock CHANGED
@@ -24,12 +24,12 @@ GEM
24
24
  date (3.4.1)
25
25
  decoor (0.1.0)
26
26
  docile (1.4.1)
27
- elapsed (0.1.0)
28
- loog (> 0)
29
- tago (> 0)
27
+ elapsed (0.2.0)
28
+ loog (~> 0.6)
29
+ tago (~> 0.1)
30
30
  ellipsized (0.3.0)
31
- erb (5.0.1)
32
- json (2.12.2)
31
+ erb (5.0.2)
32
+ json (2.13.2)
33
33
  language_server-protocol (3.17.0.5)
34
34
  lint_roller (1.1.0)
35
35
  logger (1.7.0)
@@ -41,18 +41,18 @@ GEM
41
41
  builder
42
42
  minitest (>= 5.0)
43
43
  ruby-progressbar
44
- nokogiri (1.18.8-arm64-darwin)
44
+ nokogiri (1.18.9-arm64-darwin)
45
45
  racc (~> 1.4)
46
- nokogiri (1.18.8-x64-mingw-ucrt)
46
+ nokogiri (1.18.9-x64-mingw-ucrt)
47
47
  racc (~> 1.4)
48
- nokogiri (1.18.8-x86_64-darwin)
48
+ nokogiri (1.18.9-x86_64-darwin)
49
49
  racc (~> 1.4)
50
- nokogiri (1.18.8-x86_64-linux-gnu)
50
+ nokogiri (1.18.9-x86_64-linux-gnu)
51
51
  racc (~> 1.4)
52
52
  os (1.1.4)
53
53
  others (0.1.1)
54
54
  parallel (1.27.0)
55
- parser (3.3.8.0)
55
+ parser (3.3.9.0)
56
56
  ast (~> 2.4.1)
57
57
  racc
58
58
  prism (1.4.0)
@@ -72,7 +72,7 @@ GEM
72
72
  psych (>= 4.0.0)
73
73
  regexp_parser (2.10.0)
74
74
  rexml (3.4.1)
75
- rubocop (1.77.0)
75
+ rubocop (1.79.0)
76
76
  json (~> 2.3)
77
77
  language_server-protocol (~> 3.17.0.2)
78
78
  lint_roller (~> 1.1.0)
@@ -80,10 +80,11 @@ GEM
80
80
  parser (>= 3.3.0.2)
81
81
  rainbow (>= 2.2.2, < 4.0)
82
82
  regexp_parser (>= 2.9.3, < 3.0)
83
- rubocop-ast (>= 1.45.1, < 2.0)
83
+ rubocop-ast (>= 1.46.0, < 2.0)
84
84
  ruby-progressbar (~> 1.7)
85
+ tsort (>= 0.2.0)
85
86
  unicode-display_width (>= 2.4.0, < 4.0)
86
- rubocop-ast (1.45.1)
87
+ rubocop-ast (1.46.0)
87
88
  parser (>= 3.3.7.2)
88
89
  prism (~> 1.4)
89
90
  rubocop-minitest (0.38.1)
@@ -102,16 +103,17 @@ GEM
102
103
  docile (~> 1.1)
103
104
  simplecov-html (~> 0.11)
104
105
  simplecov_json_formatter (~> 0.1)
105
- simplecov-cobertura (2.1.0)
106
+ simplecov-cobertura (3.0.0)
106
107
  rexml
107
108
  simplecov (~> 0.19)
108
- simplecov-html (0.13.1)
109
+ simplecov-html (0.13.2)
109
110
  simplecov_json_formatter (0.1.4)
110
111
  stringio (3.1.7)
111
112
  tago (0.1.0)
112
113
  threads (0.4.1)
113
114
  backtrace (~> 0)
114
115
  concurrent-ruby (~> 1.0)
116
+ tsort (0.2.0)
115
117
  unicode-display_width (3.1.4)
116
118
  unicode-emoji (~> 4.0, >= 4.0.4)
117
119
  unicode-emoji (4.0.4)
@@ -134,13 +136,13 @@ DEPENDENCIES
134
136
  os (~> 1.1)
135
137
  qbash (~> 0.4)
136
138
  rake (~> 13.2)
137
- rdoc (= 6.14.1)
139
+ rdoc (= 6.14.2)
138
140
  rubocop (~> 1.74)
139
141
  rubocop-minitest (~> 0.38)
140
142
  rubocop-performance (~> 1.25)
141
143
  rubocop-rake (~> 0.7)
142
144
  simplecov (~> 0.22)
143
- simplecov-cobertura (~> 2.1)
145
+ simplecov-cobertura (~> 3.0)
144
146
  threads (~> 0.4)
145
147
  yard (~> 0.9)
146
148
 
data/README.md CHANGED
@@ -210,33 +210,35 @@ This is the result of the benchmark:
210
210
  <!-- benchmark_begin -->
211
211
  ```text
212
212
  user
213
- insert 20000 facts 0.618307
214
- export 20000 facts 0.021391
215
- import 410726 bytes (20000 facts) 0.030150
216
- insert 10 facts 0.045651
217
- query 10 times w/txn 1.970434
218
- query 10 times w/o txn 0.039459
219
- modify 10 attrs w/txn 1.819769
220
- delete 10 facts w/txn 1.056563
221
- (and (eq what 'issue-was-closed') (exists... -> 200 2.125753
222
- (and (eq what 'issue-was-closed') (exists... -> 200/txn 1.135618
223
- (and (eq what 'issue-was-closed') (exists... -> zero 2.422668
224
- (and (eq what 'issue-was-closed') (exists... -> zero/txn 1.288236
225
- (gt time '2024-03-23T03:21:43Z') 0.108849
226
- (gt cost 50) 0.090092
227
- (eq title 'Object Thinking 5000') 0.002640
228
- (and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.023785
229
- (eq id (agg (always) (max id))) 0.252980
230
- (join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 0.655997
231
- delete! 0.075510
232
- Taped.append() x50000 0.042923
233
- Taped.each() x125 1.343819
234
- Taped.delete_if() x375 0.830124
213
+ insert 20000 facts 0.658630
214
+ export 20000 facts 0.025967
215
+ import 411020 bytes (20000 facts) 0.024282
216
+ insert 10 facts 0.050191
217
+ query 10 times w/txn 2.063964
218
+ query 10 times w/o txn 0.044326
219
+ modify 10 attrs w/txn 1.745367
220
+ delete 10 facts w/txn 1.023205
221
+ (and (eq what 'issue-was-closed') (exists... -> 200 2.043080
222
+ (and (eq what 'issue-was-closed') (exists... -> 200/txn 1.019861
223
+ (and (eq what 'issue-was-closed') (exists... -> zero 2.350912
224
+ (and (eq what 'issue-was-closed') (exists... -> zero/txn 1.187649
225
+ (gt time '2024-03-23T03:21:43Z') 0.304861
226
+ (gt cost 50) 0.234136
227
+ (eq title 'Object Thinking 5000') 0.091766
228
+ (and (eq foo 42.998) (or (gt bar 200) (absent z... 0.049974
229
+ (and (exists foo) (not (exists blue))) 0.926298
230
+ (eq id (agg (always) (max id))) 0.606111
231
+ (join "c<=cost,b<=bar" (eq id (agg (always) (ma... 1.276007
232
+ (and (eq what "foo") (join "w<=what" (and (eq i... 6.827440
233
+ delete! 0.218373
234
+ Taped.append() x50000 0.023564
235
+ Taped.each() x125 1.348705
236
+ Taped.delete_if() x375 0.812740
235
237
  ```
236
238
 
237
239
  The results were calculated in [this GHA job][benchmark-gha]
238
- on 2025-06-21 at 08:24,
240
+ on 2025-07-28 at 17:02,
239
241
  on Linux with 4 CPUs.
240
242
  <!-- benchmark_end -->
241
243
 
242
- [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/15793882855
244
+ [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/16575338618
@@ -3,12 +3,16 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
+ require 'ellipsized'
7
+ require 'timeout'
6
8
  require_relative '../lib/factbase'
7
9
 
8
10
  def bench_query(bmk, fb)
9
11
  total = 20_000
10
12
  total.times do |i|
11
13
  f = fb.insert
14
+ f.what = 'foo'
15
+ f.where = 'github'
12
16
  f.id = i
13
17
  f.title = "Object Thinking #{i}"
14
18
  f.time = Time.now.iso8601
@@ -20,7 +24,7 @@ def bench_query(bmk, fb)
20
24
  f.zzz = "Extra#{i}" if (i % 10).zero?
21
25
  end
22
26
 
23
- runs = 10
27
+ runs = 3
24
28
  [
25
29
  '(gt time \'2024-03-23T03:21:43Z\')',
26
30
  '(gt cost 50)',
@@ -28,11 +32,21 @@ def bench_query(bmk, fb)
28
32
  '(and (eq foo 42.998) (or (gt bar 200) (absent zzz)))',
29
33
  '(and (exists foo) (not (exists blue)))',
30
34
  '(eq id (agg (always) (max id)))',
31
- '(join "c<=cost,b<=bar" (eq id (agg (always) (max id))))'
35
+ '(join "c<=cost,b<=bar" (eq id (agg (always) (max id))))',
36
+ '(and
37
+ (eq what "foo")
38
+ (join "w<=what"
39
+ (and
40
+ (eq id $id)
41
+ (eq what "foo")
42
+ (eq where "github")))
43
+ (assert "has it" (one id)))'
32
44
  ].each do |q|
33
- bmk.report(q) do
34
- runs.times do
35
- fb.query(q).each.to_a
45
+ bmk.report(q.tr("\n", ' ').gsub(/\s+/, ' ').ellipsized(50, :right)) do
46
+ Timeout.timeout(runs * 3) do
47
+ runs.times do
48
+ fb.query(q).each.to_a
49
+ end
36
50
  end
37
51
  end
38
52
  end
@@ -11,7 +11,10 @@ facts:
11
11
  - person: Walter
12
12
  age: 45
13
13
  queries:
14
- - query: (and (join 'age<=age' (eq person $name)) (exists age))
14
+ - query: |
15
+ (and
16
+ (join 'age<=age' (eq person $name))
17
+ (exists age))
15
18
  size: 4
16
19
  - query: (and (join 'age' (eq person $name)) (exists age))
17
20
  size: 4
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'decoor'
7
+ require 'timeout'
8
+ require_relative 'syntax'
9
+
10
+ # A decorator of a Factbase, that terminates long-running queries.
11
+ #
12
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
13
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
14
+ # License:: MIT
15
+ class Factbase::Impatient
16
+ # Ctor.
17
+ # @param [Factbase] fb The factbase to decorate
18
+ # @param [Integer] timeout Timeout in seconds
19
+ def initialize(fb, timeout: 15)
20
+ raise 'The "fb" is nil' if fb.nil?
21
+ @origin = fb
22
+ @timeout = timeout
23
+ end
24
+
25
+ decoor(:origin)
26
+
27
+ def insert
28
+ @origin.insert
29
+ end
30
+
31
+ def query(term, maps = nil)
32
+ term = to_term(term) if term.is_a?(String)
33
+ Query.new(term, maps, @timeout, @origin)
34
+ end
35
+
36
+ def txn
37
+ @origin.txn do |fbt|
38
+ yield Factbase::Impatient.new(fbt, timeout: @timeout)
39
+ end
40
+ end
41
+
42
+ # Query decorator.
43
+ #
44
+ # This is an internal class, it is not supposed to be instantiated directly.
45
+ class Query
46
+ def initialize(term, maps, timeout, fb)
47
+ @term = term
48
+ @maps = maps
49
+ @timeout = timeout
50
+ @fb = fb
51
+ end
52
+
53
+ def to_s
54
+ @term.to_s
55
+ end
56
+
57
+ def each(fb = @fb, params = {}, &)
58
+ return to_enum(__method__, fb, params) unless block_given?
59
+ qry = @fb.query(@term, @maps)
60
+ Timeout.timeout(@timeout) do
61
+ qry.each(fb, params, &)
62
+ end
63
+ rescue Timeout::Error => e
64
+ raise "Query timed out after #{@timeout} seconds: #{e.message}"
65
+ end
66
+
67
+ def one(fb = @fb, params = {})
68
+ qry = @fb.query(@term, @maps)
69
+ Timeout.timeout(@timeout) do
70
+ qry.one(fb, params)
71
+ end
72
+ rescue Timeout::Error => e
73
+ raise "Query timed out after #{@timeout} seconds: #{e.message}"
74
+ end
75
+
76
+ def delete!(fb = @fb)
77
+ qry = @fb.query(@term, @maps)
78
+ Timeout.timeout(@timeout) do
79
+ qry.delete!(fb)
80
+ end
81
+ rescue Timeout::Error => e
82
+ raise "Query timed out after #{@timeout} seconds: #{e.message}"
83
+ end
84
+ end
85
+ end
@@ -94,10 +94,7 @@ module Factbase::IndexedTerm
94
94
  else
95
95
  @operands.each do |o|
96
96
  n = o.predict(maps, params)
97
- if n.nil?
98
- r = nil
99
- break
100
- end
97
+ break if n.nil?
101
98
  if r.nil?
102
99
  r = n
103
100
  else
@@ -45,7 +45,8 @@ class Factbase::Query
45
45
  yielded = 0
46
46
  params = params.transform_keys(&:to_s) if params.is_a?(Hash)
47
47
  maybe = @term.predict(@maps, Factbase::Tee.new({}, params))
48
- (maybe || @maps).each do |m|
48
+ maybe ||= @maps unless maybe.equal?(@maps)
49
+ maybe.each do |m|
49
50
  extras = {}
50
51
  f = Factbase::Fact.new(m)
51
52
  f = Factbase::Tee.new(f, params)
data/lib/factbase/term.rb CHANGED
@@ -90,6 +90,7 @@ class Factbase::Term
90
90
  # @param [Hash] args Attributes to set
91
91
  def redress!(type, **args)
92
92
  extend type
93
+
93
94
  args.each { |k, v| send(:instance_variable_set, :"@#{k}", v) }
94
95
  @operands.map do |op|
95
96
  if op.is_a?(Factbase::Term)
@@ -163,19 +164,22 @@ class Factbase::Term
163
164
  # Turns it into a string.
164
165
  # @return [String] The string of it
165
166
  def to_s
166
- items = []
167
- items << @op
168
- items +=
169
- @operands.map do |o|
170
- if o.is_a?(String)
171
- "'#{o.gsub("'", "\\\\'").gsub('"', '\\\\"')}'"
172
- elsif o.is_a?(Time)
173
- o.utc.iso8601
174
- else
175
- o.to_s
176
- end
167
+ @to_s ||=
168
+ begin
169
+ items = []
170
+ items << @op
171
+ items +=
172
+ @operands.map do |o|
173
+ if o.is_a?(String)
174
+ "'#{o.gsub("'", "\\\\'").gsub('"', '\\\\"')}'"
175
+ elsif o.is_a?(Time)
176
+ o.utc.iso8601
177
+ else
178
+ o.to_s
179
+ end
180
+ end
181
+ "(#{items.join(' ')})"
177
182
  end
178
- "(#{items.join(' ')})"
179
183
  end
180
184
 
181
185
  def at(fact, maps, fb)
@@ -9,5 +9,5 @@
9
9
  # License:: MIT
10
10
  class Factbase
11
11
  # Current version of the gem (changed by .rultor.yml on every release)
12
- VERSION = '0.13.0' unless const_defined?(:VERSION)
12
+ VERSION = '0.14.1' unless const_defined?(:VERSION)
13
13
  end
@@ -4,7 +4,9 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require_relative '../../test__helper'
7
+ require_relative '../../../lib/factbase/accum'
7
8
  require_relative '../../../lib/factbase/term'
9
+ require_relative '../../../lib/factbase/syntax'
8
10
 
9
11
  # Logical test.
10
12
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../test__helper'
7
+ require_relative '../../lib/factbase'
8
+ require_relative '../../lib/factbase/query'
9
+ require_relative '../../lib/factbase/impatient'
10
+
11
+ # Test.
12
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
13
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
14
+ # License:: MIT
15
+ class TestImpatient < Factbase::Test
16
+ class SlowFactbase < Factbase
17
+ class SlowQuery < Factbase::Query
18
+ def one(fb = @fb, params = {})
19
+ sleep 0.2
20
+ super
21
+ end
22
+ end
23
+
24
+ def query(term, maps = nil)
25
+ maps ||= @maps
26
+ term = to_term(term) if term.is_a?(String)
27
+ SlowQuery.new(maps, term, self)
28
+ end
29
+ end
30
+
31
+ class SlowDeleteFactbase < Factbase
32
+ class SlowQuery < Factbase::Query
33
+ def delete!(fb = @fb)
34
+ sleep 0.2
35
+ super
36
+ end
37
+ end
38
+
39
+ def query(term, maps = nil)
40
+ maps ||= @maps
41
+ term = to_term(term) if term.is_a?(String)
42
+ SlowQuery.new(maps, term, self)
43
+ end
44
+ end
45
+
46
+ class SlowEnoughFactbase < Factbase
47
+ class SlowQuery < Factbase::Query
48
+ def one(fb = @fb, params = {})
49
+ sleep 1.5
50
+ super
51
+ end
52
+ end
53
+
54
+ def query(term, maps = nil)
55
+ maps ||= @maps
56
+ term = to_term(term) if term.is_a?(String)
57
+ SlowQuery.new(maps, term, self)
58
+ end
59
+ end
60
+
61
+ def test_simple_query
62
+ fb = Factbase::Impatient.new(Factbase.new)
63
+ fb.insert
64
+ fb.insert.bar = 3
65
+ found = 0
66
+ fb.query('(exists bar)').each do |f|
67
+ assert_predicate(f.bar, :positive?)
68
+ f.foo = 42
69
+ assert_equal(42, f.foo)
70
+ found += 1
71
+ end
72
+ assert_equal(1, found)
73
+ assert_equal(2, fb.size)
74
+ end
75
+
76
+ def test_query_one
77
+ fb = Factbase::Impatient.new(Factbase.new)
78
+ fb.insert
79
+ fb.insert.bar = 42
80
+ assert_equal(1, fb.query('(agg (exists bar) (count))').one)
81
+ assert_equal([42], fb.query('(agg (exists bar) (first bar))').one)
82
+ end
83
+
84
+ def test_query_timeout
85
+ fb = Factbase::Impatient.new(Factbase.new, timeout: 0.1)
86
+ 1000.times do
87
+ fb.insert.value = rand(1000)
88
+ end
89
+ ex =
90
+ assert_raises(StandardError) do
91
+ fb.query('(always)').each do
92
+ sleep 0.2
93
+ end
94
+ end
95
+ assert_includes(ex.message, 'Query timed out after 0.1 seconds')
96
+ end
97
+
98
+ def test_query_one_timeout
99
+ slow = SlowFactbase.new
100
+ 10_000.times do
101
+ slow.insert.value = rand(1000)
102
+ end
103
+ fb = Factbase::Impatient.new(slow, timeout: 0.01)
104
+ ex =
105
+ assert_raises(StandardError) do
106
+ fb.query('(agg (min value))').one
107
+ end
108
+ assert_includes(ex.message, 'Query timed out after 0.01 seconds')
109
+ end
110
+
111
+ def test_delete_timeout
112
+ slow = SlowDeleteFactbase.new
113
+ 1000.times do |i|
114
+ slow.insert.value = i
115
+ end
116
+ fb = Factbase::Impatient.new(slow, timeout: 0.01)
117
+ ex =
118
+ assert_raises(StandardError) do
119
+ fb.query('(gt value 500)').delete!
120
+ end
121
+ assert_includes(ex.message, 'Query timed out after 0.01 seconds')
122
+ end
123
+
124
+ def test_with_txn
125
+ fb = Factbase::Impatient.new(Factbase.new)
126
+ assert(
127
+ fb.txn do |fbt|
128
+ fbt.insert.foo = 42
129
+ end
130
+ )
131
+ assert_equal(1, fb.size)
132
+ end
133
+
134
+ def test_with_txn_timeout
135
+ fb = Factbase::Impatient.new(Factbase.new, timeout: 0.1)
136
+ fb.txn do |fbt|
137
+ fbt.insert.slow = 42
138
+ ex =
139
+ assert_raises(StandardError) do
140
+ fbt.query('(always)').each do
141
+ sleep 0.2
142
+ end
143
+ end
144
+ assert_includes(ex.message, 'Query timed out after 0.1 seconds')
145
+ end
146
+ end
147
+
148
+ def test_returns_int
149
+ fb = Factbase.new
150
+ fb.insert
151
+ fb.insert
152
+ assert_equal(2, Factbase::Impatient.new(fb).query('(always)').each(&:to_s))
153
+ end
154
+
155
+ def test_returns_int_when_empty
156
+ fb = Factbase.new
157
+ assert_equal(0, Factbase::Impatient.new(fb).query('(always)').each(&:to_s))
158
+ end
159
+
160
+ def test_returns_to_s_correctly
161
+ fb = Factbase.new
162
+ q = '(always)'
163
+ assert_equal(q, fb.query(q).to_s)
164
+ end
165
+
166
+ def test_enumerator_support
167
+ fb = Factbase::Impatient.new(Factbase.new)
168
+ assert_equal(0, fb.query('(always)').each.to_a.size)
169
+ fb.insert
170
+ assert_equal(1, fb.query('(always)').each.to_a.size)
171
+ end
172
+
173
+ def test_query_completes_before_timeout
174
+ fb = Factbase::Impatient.new(Factbase.new, timeout: 1)
175
+ 100.times do |i|
176
+ fb.insert.index = i
177
+ end
178
+ count = 0
179
+ fb.query('(always)').each do |f|
180
+ count += 1
181
+ assert_equal(count - 1, f.index)
182
+ end
183
+ assert_equal(100, count)
184
+ end
185
+
186
+ def test_custom_timeout
187
+ slow = SlowEnoughFactbase.new
188
+ slow.insert.value = 42
189
+ fb = Factbase::Impatient.new(slow, timeout: 2)
190
+ start = Time.now
191
+ result = fb.query('(agg (eq value 42) (first value))').one
192
+ elapsed = Time.now - start
193
+ assert_operator(elapsed, :>=, 1.5)
194
+ assert_equal([42], result)
195
+ end
196
+
197
+ def test_nil_factbase_raises
198
+ ex =
199
+ assert_raises(StandardError) do
200
+ Factbase::Impatient.new(nil)
201
+ end
202
+ assert_equal('The "fb" is nil', ex.message)
203
+ end
204
+ end
@@ -10,6 +10,7 @@ require_relative '../../lib/factbase'
10
10
  require_relative '../../lib/factbase/query'
11
11
  require_relative '../../lib/factbase/logged'
12
12
  require_relative '../../lib/factbase/pre'
13
+ require_relative '../../lib/factbase/impatient'
13
14
  require_relative '../../lib/factbase/inv'
14
15
  require_relative '../../lib/factbase/rules'
15
16
  require_relative '../../lib/factbase/tallied'
@@ -314,6 +315,7 @@ class TestQuery < Factbase::Test
314
315
  def with_factbases(maps = [], &)
315
316
  {
316
317
  'plain' => Factbase.new(maps),
318
+ 'plain+impatient' => Factbase::Impatient.new(Factbase.new(maps)),
317
319
  'pre+plain' => Factbase::Pre.new(Factbase.new(maps)) { nil },
318
320
  'rules+plain' => Factbase::Rules.new(Factbase.new(maps), '(always)'),
319
321
  'inv+plain' => Factbase::Inv.new(Factbase.new(maps)) { nil },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -218,6 +218,7 @@ files:
218
218
  - lib/factbase/fact.rb
219
219
  - lib/factbase/fact_as_yaml.rb
220
220
  - lib/factbase/flatten.rb
221
+ - lib/factbase/impatient.rb
221
222
  - lib/factbase/indexed/indexed_fact.rb
222
223
  - lib/factbase/indexed/indexed_factbase.rb
223
224
  - lib/factbase/indexed/indexed_query.rb
@@ -275,6 +276,7 @@ files:
275
276
  - test/factbase/test_fact.rb
276
277
  - test/factbase/test_fact_as_yaml.rb
277
278
  - test/factbase/test_flatten.rb
279
+ - test/factbase/test_impatient.rb
278
280
  - test/factbase/test_inv.rb
279
281
  - test/factbase/test_logged.rb
280
282
  - test/factbase/test_pre.rb