factbase 0.12.1 → 0.14.0

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: 1ba24c42ea5df6fab3bfcf4477eff165173de5eb224c80e8bd1b1eeda5a9036d
4
- data.tar.gz: b0f9362eec0b148f451dcbc94012db27ac97a02e4c6fbe48e0303a33d5f46c9e
3
+ metadata.gz: e275580781c232bdbea9b569d456ff3955c4a1fe1add33101398d00acc9605e5
4
+ data.tar.gz: 5c3a6d0e61c81a8bc7a2cc0a3ca6f003a77ad0cd9fdae1d68e2ecb55d386b169
5
5
  SHA512:
6
- metadata.gz: e6460f586f42569fac5f6dedd783ab68e47f1fc2e7e05932033003586b40339044005d383306fce26f3f56a375d7a33d3a2798d3b84356e76c09550c6375e3d9
7
- data.tar.gz: c8067f91613d87093ebf824c4800298e2b2e25654fc4ad9b5c3fed4c26e8265e19968a8eac887e5b541de48e5ebc531df6925c4ad73c48b49b1cb80ba429e5b7
6
+ metadata.gz: 0d764be837fc143d2a39b7f58d40cc33f93a3fb37a8608cca74b514b41a1ecbfac64c5e4b5bde836fa414fb33ae6cd0c7b7668802a8431f6a6f3a428a3bd6f90
7
+ data.tar.gz: 4aa141144fb2b41a4b6a47b0ca2acde9b3c35d1c8bd487700e9fe0d88a19ea6eac0224dd0390969a3377856b0f84d1af3d54f6a870ce63300065f151e2e828f9
@@ -16,4 +16,4 @@ jobs:
16
16
  runs-on: ubuntu-24.04
17
17
  steps:
18
18
  - uses: actions/checkout@v4
19
- - uses: crate-ci/typos@v1.33.1
19
+ - uses: crate-ci/typos@v1.34.0
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.13', require: false
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.0.1)
27
+ elapsed (0.1.0)
28
28
  loog (> 0)
29
29
  tago (> 0)
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.0)
33
33
  language_server-protocol (3.17.0.5)
34
34
  lint_roller (1.1.0)
35
35
  logger (1.7.0)
@@ -41,13 +41,13 @@ 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)
@@ -67,12 +67,12 @@ GEM
67
67
  racc (1.8.1)
68
68
  rainbow (3.1.1)
69
69
  rake (13.3.0)
70
- rdoc (6.14.1)
70
+ rdoc (6.14.2)
71
71
  erb
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.78.0)
76
76
  json (~> 2.3)
77
77
  language_server-protocol (~> 3.17.0.2)
78
78
  lint_roller (~> 1.1.0)
@@ -83,7 +83,7 @@ GEM
83
83
  rubocop-ast (>= 1.45.1, < 2.0)
84
84
  ruby-progressbar (~> 1.7)
85
85
  unicode-display_width (>= 2.4.0, < 4.0)
86
- rubocop-ast (1.45.1)
86
+ rubocop-ast (1.46.0)
87
87
  parser (>= 3.3.7.2)
88
88
  prism (~> 1.4)
89
89
  rubocop-minitest (0.38.1)
@@ -102,10 +102,10 @@ GEM
102
102
  docile (~> 1.1)
103
103
  simplecov-html (~> 0.11)
104
104
  simplecov_json_formatter (~> 0.1)
105
- simplecov-cobertura (2.1.0)
105
+ simplecov-cobertura (3.0.0)
106
106
  rexml
107
107
  simplecov (~> 0.19)
108
- simplecov-html (0.13.1)
108
+ simplecov-html (0.13.2)
109
109
  simplecov_json_formatter (0.1.4)
110
110
  stringio (3.1.7)
111
111
  tago (0.1.0)
@@ -134,13 +134,13 @@ DEPENDENCIES
134
134
  os (~> 1.1)
135
135
  qbash (~> 0.4)
136
136
  rake (~> 13.2)
137
- rdoc (~> 6.13)
137
+ rdoc (= 6.14.2)
138
138
  rubocop (~> 1.74)
139
139
  rubocop-minitest (~> 0.38)
140
140
  rubocop-performance (~> 1.25)
141
141
  rubocop-rake (~> 0.7)
142
142
  simplecov (~> 0.22)
143
- simplecov-cobertura (~> 2.1)
143
+ simplecov-cobertura (~> 3.0)
144
144
  threads (~> 0.4)
145
145
  yard (~> 0.9)
146
146
 
@@ -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
@@ -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.12.1' unless const_defined?(:VERSION)
12
+ VERSION = '0.14.0' unless const_defined?(:VERSION)
13
13
  end
data/lib/factbase.rb CHANGED
@@ -177,7 +177,12 @@ class Factbase
177
177
  taped = Factbase::Taped.new(before)
178
178
  catch :commit do
179
179
  require_relative 'factbase/light'
180
- yield Factbase::Light.new(Factbase.new(taped))
180
+ commit = false
181
+ catch :rollback do
182
+ yield Factbase::Light.new(Factbase.new(taped))
183
+ commit = true
184
+ end
185
+ return 0 unless commit
181
186
  rescue Factbase::Rollback
182
187
  return 0
183
188
  end
@@ -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_fb = SlowFactbase.new
100
+ 1000.times do
101
+ slow_fb.insert.value = rand(1000)
102
+ end
103
+ fb = Factbase::Impatient.new(slow_fb, timeout: 0.1)
104
+ ex =
105
+ assert_raises(StandardError) do
106
+ fb.query('(always)').one
107
+ end
108
+ assert_includes(ex.message, 'Query timed out after 0.1 seconds')
109
+ end
110
+
111
+ def test_delete_timeout
112
+ slow_fb = SlowDeleteFactbase.new
113
+ 1000.times do |i|
114
+ slow_fb.insert.value = i
115
+ end
116
+ fb = Factbase::Impatient.new(slow_fb, timeout: 0.1)
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.1 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_fb = SlowEnoughFactbase.new
188
+ slow_fb.insert.value = 42
189
+ fb = Factbase::Impatient.new(slow_fb, 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 },
@@ -486,6 +486,15 @@ class TestFactbase < Factbase::Test
486
486
  assert_equal(1, fb.size)
487
487
  end
488
488
 
489
+ def test_rolls_back
490
+ fb = Factbase.new
491
+ fb.txn do |fbt|
492
+ fbt.insert.foo = 1
493
+ throw :rollback
494
+ end
495
+ assert_equal(0, fb.size)
496
+ end
497
+
489
498
  def test_get_raise_for_empty_fact
490
499
  fb = Factbase.new
491
500
  fb.txn do |fbt|
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.12.1
4
+ version: 0.14.0
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