factbase 0.15.0 → 0.15.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -5
  3. data/factbase.gemspec +4 -4
  4. data/lib/factbase/fact.rb +1 -1
  5. data/lib/factbase/fact_as_yaml.rb +10 -1
  6. data/lib/factbase/indexed/indexed_term.rb +6 -1
  7. data/lib/factbase/version.rb +1 -1
  8. metadata +7 -87
  9. data/.0pdd.yml +0 -8
  10. data/.gitattributes +0 -7
  11. data/.github/publish-benchmark.sh +0 -24
  12. data/.github/workflows/actionlint.yml +0 -25
  13. data/.github/workflows/benchmark.yml +0 -36
  14. data/.github/workflows/codecov.yml +0 -27
  15. data/.github/workflows/copyrights.yml +0 -19
  16. data/.github/workflows/markdown-lint.yml +0 -23
  17. data/.github/workflows/pdd.yml +0 -19
  18. data/.github/workflows/rake.yml +0 -28
  19. data/.github/workflows/reuse.yml +0 -19
  20. data/.github/workflows/typos.yml +0 -19
  21. data/.github/workflows/xcop.yml +0 -19
  22. data/.github/workflows/yamllint.yml +0 -19
  23. data/.gitignore +0 -11
  24. data/.pdd +0 -7
  25. data/.rubocop.yml +0 -51
  26. data/.rultor.yml +0 -27
  27. data/benchmark/bench_factbase.rb +0 -61
  28. data/benchmark/bench_large_query.rb +0 -128
  29. data/benchmark/bench_query.rb +0 -57
  30. data/benchmark/bench_taped.rb +0 -33
  31. data/fixtures/stories/agg.yml +0 -17
  32. data/fixtures/stories/always.yml +0 -16
  33. data/fixtures/stories/and.yml +0 -19
  34. data/fixtures/stories/as.yml +0 -16
  35. data/fixtures/stories/count.yml +0 -18
  36. data/fixtures/stories/empty.yml +0 -23
  37. data/fixtures/stories/eq.yml +0 -30
  38. data/fixtures/stories/gt.yml +0 -18
  39. data/fixtures/stories/join.yml +0 -22
  40. data/fixtures/stories/max.yml +0 -14
  41. data/fixtures/stories/min.yml +0 -14
  42. data/fixtures/stories/nth.yml +0 -14
  43. data/fixtures/stories/one.yml +0 -14
  44. data/fixtures/stories/or.yml +0 -18
  45. data/fixtures/stories/sprintf.yml +0 -12
  46. data/fixtures/stories/sum.yml +0 -14
  47. data/fixtures/stories/unique.yml +0 -30
  48. data/renovate.json +0 -6
  49. data/test/factbase/cached/test_cached_factbase.rb +0 -44
  50. data/test/factbase/cached/test_cached_query.rb +0 -100
  51. data/test/factbase/indexed/test_indexed_fact.rb +0 -23
  52. data/test/factbase/indexed/test_indexed_factbase.rb +0 -96
  53. data/test/factbase/indexed/test_indexed_query.rb +0 -208
  54. data/test/factbase/indexed/test_indexed_term.rb +0 -140
  55. data/test/factbase/sync/test_sync_factbase.rb +0 -22
  56. data/test/factbase/sync/test_sync_query.rb +0 -30
  57. data/test/factbase/terms/test_aggregates.rb +0 -63
  58. data/test/factbase/terms/test_aliases.rb +0 -58
  59. data/test/factbase/terms/test_casting.rb +0 -33
  60. data/test/factbase/terms/test_debug.rb +0 -111
  61. data/test/factbase/terms/test_defn.rb +0 -48
  62. data/test/factbase/terms/test_logical.rb +0 -57
  63. data/test/factbase/terms/test_math.rb +0 -122
  64. data/test/factbase/terms/test_meta.rb +0 -70
  65. data/test/factbase/terms/test_ordering.rb +0 -44
  66. data/test/factbase/terms/test_strings.rb +0 -36
  67. data/test/factbase/terms/test_system.rb +0 -31
  68. data/test/factbase/test_accum.rb +0 -74
  69. data/test/factbase/test_churn.rb +0 -41
  70. data/test/factbase/test_fact.rb +0 -108
  71. data/test/factbase/test_fact_as_yaml.rb +0 -77
  72. data/test/factbase/test_flatten.rb +0 -28
  73. data/test/factbase/test_impatient.rb +0 -176
  74. data/test/factbase/test_inv.rb +0 -44
  75. data/test/factbase/test_logged.rb +0 -155
  76. data/test/factbase/test_pre.rb +0 -35
  77. data/test/factbase/test_query.rb +0 -445
  78. data/test/factbase/test_rules.rb +0 -128
  79. data/test/factbase/test_syntax.rb +0 -166
  80. data/test/factbase/test_tallied.rb +0 -81
  81. data/test/factbase/test_taped.rb +0 -72
  82. data/test/factbase/test_tee.rb +0 -85
  83. data/test/factbase/test_term.rb +0 -87
  84. data/test/factbase/test_to_json.rb +0 -35
  85. data/test/factbase/test_to_xml.rb +0 -89
  86. data/test/factbase/test_to_yaml.rb +0 -39
  87. data/test/test__helper.rb +0 -41
  88. data/test/test_factbase.rb +0 -509
@@ -1,35 +0,0 @@
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 'loog'
8
- require_relative '../../lib/factbase/pre'
9
-
10
- # Test.
11
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
12
- # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
13
- # License:: MIT
14
- class TestPre < Factbase::Test
15
- def test_simple_setting
16
- fb = Factbase::Pre.new(Factbase.new) { |f| f.foo = 42 }
17
- f = fb.insert
18
- assert_equal(42, f.foo)
19
- assert_equal(1, fb.query('(always)').each.to_a.size)
20
- end
21
-
22
- def test_in_transaction
23
- fb =
24
- Factbase::Pre.new(Factbase.new) do |f, fbt|
25
- f.total = fbt.size
26
- end
27
- fb.txn do |fbt|
28
- fbt.insert
29
- fbt.insert
30
- end
31
- arr = fb.query('(always)').each.to_a
32
- assert_equal(1, arr[0].total)
33
- assert_equal(2, arr[1].total)
34
- end
35
- end
@@ -1,445 +0,0 @@
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 'loog'
8
- require 'time'
9
- require_relative '../../lib/factbase'
10
- require_relative '../../lib/factbase/query'
11
- require_relative '../../lib/factbase/logged'
12
- require_relative '../../lib/factbase/pre'
13
- require_relative '../../lib/factbase/impatient'
14
- require_relative '../../lib/factbase/inv'
15
- require_relative '../../lib/factbase/rules'
16
- require_relative '../../lib/factbase/tallied'
17
- require_relative '../../lib/factbase/cached/cached_factbase'
18
- require_relative '../../lib/factbase/indexed/indexed_factbase'
19
- require_relative '../../lib/factbase/sync/sync_factbase'
20
-
21
- # Query test.
22
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
23
- # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
24
- # License:: MIT
25
- class TestQuery < Factbase::Test
26
- def test_stories
27
- with_factbases do |badge, fb|
28
- Dir[File.join(__dir__, '../../fixtures/stories/**/*.yml')].each do |fixture|
29
- base = File.basename(fixture)
30
- story = YAML.load_file(fixture)
31
- 2.times do
32
- fb.query('(always)').delete!
33
- story['facts'].each do |y|
34
- f = fb.insert
35
- y.each do |k, vv|
36
- vv = [vv] unless vv.is_a?(Array)
37
- vv.each do |v|
38
- f.send(:"#{k}=", v)
39
- end
40
- end
41
- end
42
- story['queries'].each do |q|
43
- qry = q['query']
44
- if q['size']
45
- size = q['size']
46
- assert_equal(size, fb.query(qry).each.to_a.size, "#{base}: #{qry} at #{badge}")
47
- fb.txn do |fbt|
48
- facts = fbt.query(qry).each.to_a
49
- assert_equal(size, facts.size, "#{base}: #{qry} at #{badge} (in txn)")
50
- facts.each do |fact|
51
- refute_empty(fact.all_properties)
52
- refute_nil(fact.to_s)
53
- end
54
- end
55
- else
56
- ret = q['one']
57
- assert_equal(ret, fb.query(qry).one, "#{base}: #{qry} at #{badge}")
58
- fb.txn do |fbt|
59
- assert_equal(ret, fbt.query(qry).one, "#{base}: #{qry} at #{badge} (in txn)")
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
67
-
68
- def test_simple_parsing
69
- maps = []
70
- maps << { 'foo' => [42] }
71
- q = Factbase::Query.new(maps, '(eq foo 42)', Factbase.new)
72
- assert_equal(
73
- 1,
74
- q.each do |f|
75
- assert_equal(42, f.foo)
76
- end
77
- )
78
- end
79
-
80
- def test_complex_parsing
81
- queries = {
82
- '(eq num 444)' => 0,
83
- '(eq hi 4)' => 1,
84
- '(eq time 0)' => 0,
85
- '(gt num 60)' => 1,
86
- '(gt hi 3)' => 1,
87
- "(and (lt pi 100) \n\n (gt num 1000))" => 0,
88
- '(exists pi)' => 1,
89
- '(eq pi +3.14)' => 1,
90
- '(not (exists hello))' => 3,
91
- '(eq "Integer" (type num))' => 2,
92
- '(eq "Integer" (type hi))' => 1,
93
- '(when (eq num 0) (exists time))' => 2,
94
- '(unique num)' => 2,
95
- '(unique name)' => 2,
96
- '(unique pi)' => 1,
97
- '(many num)' => 1,
98
- '(one num)' => 2,
99
- '(nil (agg (exists hello) (min num)))' => 3,
100
- '(gt num (minus 1 (either (at 0 (prev num)) 0)))' => 3,
101
- '(and (not (many num)) (eq num (plus 21 +21)))' => 1,
102
- '(and (not (many num)) (eq num (minus -100 -142)))' => 1,
103
- '(and (one num) (eq num (times 7 6)))' => 1,
104
- '(and (one pi) (eq pi (div -6.28 -2)))' => 1,
105
- '(gt (size num) 2)' => 1,
106
- '(matches name "^[a-z]+$")' => 1,
107
- '(matches nome "^Walter$")' => 1,
108
- '(lt (size num) 2)' => 2,
109
- '(eq (size _hello) 0)' => 3,
110
- '(eq num pi)' => 0,
111
- '(absent time)' => 2,
112
- '(eq pi (agg (eq num 0) (sum pi)))' => 1,
113
- '(eq num (agg (exists oops) (count)))' => 2,
114
- '(lt num (agg (eq num 0) (max pi)))' => 2,
115
- '(eq time (min time))' => 1,
116
- '(and (absent time) (exists pi))' => 1,
117
- "(and (exists time) (not (\t\texists pi)))" => 1,
118
- '(undef something)' => 3,
119
- "(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1,
120
- '(eq 3 (agg (eq num $num) (count)))' => 1,
121
- '(and (eq num 42) (not (empty (eq name "Jeff"))))' => 2,
122
- '(and (eq num 42) (empty (eq x $name)))' => 2,
123
- '(and (eq num 42) (not (empty (eq name $name))))' => 2
124
- }
125
- maps = [
126
- { 'num' => [42], 'name' => ['Jeff'] },
127
- { 'num' => [42, 66, 0], 'pi' => [3.14], 'name' => ['peter'] },
128
- { 'num' => [0], 'time' => [Time.now - 100], 'hi' => [4], 'nome' => ['Walter'] }
129
- ]
130
- 3.times do |cycle|
131
- with_factbases(maps) do |badge, fb|
132
- queries.each do |q, r|
133
- assert_equal(r, fb.query(q).each.to_a.size, "#{q} in #{badge} (cycle ##{cycle})")
134
- fb.txn do |fbt|
135
- assert_equal(r, fbt.query(q).each.to_a.size, "#{q} in #{badge} (txn, cycle ##{cycle})")
136
- end
137
- end
138
- end
139
- end
140
- end
141
-
142
- def test_simple_parsing_with_time
143
- maps = []
144
- now = Time.now.utc
145
- maps << { 'foo' => [now] }
146
- q = Factbase::Query.new(maps, "(eq foo #{now.iso8601})", Factbase.new)
147
- assert_equal(1, q.each.to_a.size)
148
- end
149
-
150
- def test_simple_deleting
151
- maps = [
152
- { 'foo' => [42] },
153
- { 'bar' => [4, 5] },
154
- { 'bar' => [5] }
155
- ]
156
- q = Factbase::Query.new(maps, '(eq bar 5)', Factbase.new)
157
- assert_equal(2, q.delete!)
158
- assert_equal(1, maps.size)
159
- end
160
-
161
- def test_reading_one
162
- maps = [
163
- { 'foo' => [42], 'hello' => [4] },
164
- { 'bar' => [4, 5] }
165
- ]
166
- with_factbases(maps) do |badge, fb|
167
- {
168
- '(agg (and (eq foo 42) (eq hello $v)) (min foo))' => 42,
169
- '(agg (or (eq foo 42) (eq bar 4)) (min foo))' => 42,
170
- '(agg (exists foo) (first foo))' => [42],
171
- '(agg (exists z) (first z))' => nil,
172
- '(agg (always) (count))' => 2,
173
- '(agg (eq bar $v) (count))' => 1,
174
- '(agg (eq foo 42) (min foo))' => 42,
175
- '(agg (and (eq foo 42)) (min foo))' => 42,
176
- '(agg (eq z 40) (count))' => 0
177
- }.each do |q, expected|
178
- result = fb.query(q).one(fb, 'v' => 4)
179
- if expected.nil?
180
- assert_nil(result, "#{q} -> nil in #{badge}")
181
- else
182
- assert_equal(expected, result, "#{q} -> #{expected} in #{badge}")
183
- end
184
- end
185
- end
186
- end
187
-
188
- def test_deleting_nothing
189
- maps = [
190
- { 'foo' => [42] },
191
- { 'bar' => [4, 5] },
192
- { 'bar' => [5] }
193
- ]
194
- with_factbases(maps) do |badge, fb|
195
- q = fb.query('(never)')
196
- assert_equal(0, q.delete!, "#{q} in #{badge}")
197
- assert_equal(3, maps.size, "#{q} in #{badge}")
198
- end
199
- end
200
-
201
- def test_finds_with_substitution
202
- maps = [{ 'foo' => [42] }, { 'bar' => [7] }, { 'foo' => [666] }]
203
- with_factbases(maps) do |badge, fb|
204
- assert_equal(0, fb.query('(eq 2 (agg (eq foo $foo) (count)))').each.to_a.size, "with #{badge}")
205
- fb.txn do |fbt|
206
- assert_equal(0, fbt.query('(eq 2 (agg (eq foo $foo) (count)))').each.to_a.size, "with #{badge} (txn)")
207
- end
208
- end
209
- end
210
-
211
- def test_scans_and_inserts
212
- with_factbases do |_, fb|
213
- fb.insert.foo = 42
214
- before = fb.size
215
- more = 0
216
- fb.query('(exists foo)').each do |f|
217
- fb.insert.bar = f.foo
218
- more += 1
219
- end
220
- assert_equal(before + more, fb.size)
221
- end
222
- end
223
-
224
- def test_scans_and_inserts_in_txn
225
- with_factbases do |_, fb|
226
- fb.insert.foo = 42
227
- before = fb.size
228
- more = 0
229
- fb.query('(exists foo)').each do |f|
230
- fb.txn do |fbt|
231
- fbt.insert.bar = f.foo
232
- more += 1
233
- end
234
- end
235
- assert_equal(before + more, fb.size)
236
- end
237
- end
238
-
239
- def test_scans_and_inserts_in_queried_txn
240
- with_factbases do |_, fb|
241
- fb.insert.foo = 42
242
- before = fb.size
243
- more = 0
244
- fb.txn do |fbt|
245
- fbt.query('(exists foo)').each do |f|
246
- fbt.insert.bar = f.foo
247
- more += 1
248
- end
249
- end
250
- assert_equal(before + more, fb.size)
251
- end
252
- end
253
-
254
- def test_scans_and_appends_in_queried_txn
255
- with_factbases do |badge, fb|
256
- fb.insert.foo = 42
257
- fb.txn do |fbt|
258
- fbt.query('(exists foo)').each do |f|
259
- f.bar = 33
260
- end
261
- refute_empty(fbt.query('(exists bar)').each.to_a, "in #{badge}")
262
- end
263
- refute_empty(fb.query('(exists bar)').each.to_a, "in #{badge}")
264
- assert_empty(fb.query('(and (exists foo) (not (exists bar)))').each.to_a, "in #{badge}")
265
- end
266
- end
267
-
268
- def test_turns_query_to_string
269
- with_factbases do |badge, fb|
270
- assert_equal('(always)', fb.query('(always)').to_s, "Fails with #{badge}")
271
- end
272
- end
273
-
274
- def test_to_array
275
- maps = []
276
- maps << { 'foo' => [42] }
277
- assert_equal(1, Factbase::Query.new(maps, '(eq foo 42)', Factbase.new).each.to_a.size)
278
- end
279
-
280
- def test_returns_int
281
- maps = []
282
- maps << { 'foo' => [1] }
283
- q = Factbase::Query.new(maps, '(eq foo 1)', Factbase.new)
284
- assert_equal(1, q.each(&:to_s))
285
- end
286
-
287
- def test_with_aliases
288
- maps = []
289
- maps << { 'foo' => [42] }
290
- assert_equal(45, Factbase::Query.new(maps, '(as bar (plus foo 3))', Factbase.new).each.to_a[0].bar)
291
- assert_equal(1, maps[0].size)
292
- end
293
-
294
- def test_with_params
295
- maps = [
296
- { 'foo' => [42] },
297
- { 'foo' => [17] }
298
- ]
299
- found = 0
300
- Factbase::Query.new(maps, '(eq foo $bar)', Factbase.new).each(Factbase.new, bar: [42]) do
301
- found += 1
302
- end
303
- assert_equal(1, found)
304
- assert_equal(1, Factbase::Query.new(maps, '(eq foo $bar)', Factbase.new).each(Factbase.new, bar: [42]).to_a.size)
305
- assert_equal(0, Factbase::Query.new(maps, '(eq foo $bar)', Factbase.new).each(Factbase.new, bar: [555]).to_a.size)
306
- end
307
-
308
- def test_with_nil_alias
309
- maps = [{ 'foo' => [42] }]
310
- assert_nil(Factbase::Query.new(maps, '(as bar (plus xxx 3))', Factbase.new).each.to_a[0]['bar'])
311
- end
312
-
313
- def test_get_all_properties
314
- maps = [{ 'foo' => [42] }]
315
- f = Factbase::Query.new(maps, '(always)', Factbase.new).each.to_a[0]
316
- assert_includes(f.all_properties, 'foo')
317
- end
318
-
319
- private
320
-
321
- def with_factbases(maps = [], &)
322
- {
323
- 'plain' => Factbase.new(maps),
324
- 'plain+impatient' => Factbase::Impatient.new(Factbase.new(maps)),
325
- 'pre+plain' => Factbase::Pre.new(Factbase.new(maps)) { nil },
326
- 'rules+plain' => Factbase::Rules.new(Factbase.new(maps), '(always)'),
327
- 'inv+plain' => Factbase::Inv.new(Factbase.new(maps)) { nil },
328
- 'sync+plain' => Factbase::SyncFactbase.new(Factbase.new(maps)),
329
- 'tallied+plain' => Factbase::Tallied.new(Factbase.new(maps)),
330
- 'indexed+plain' => Factbase::IndexedFactbase.new(Factbase.new(maps)),
331
- 'indexed+logged+plain' => Factbase::IndexedFactbase.new(
332
- Factbase::Logged.new(
333
- Factbase.new(maps),
334
- Loog::NULL
335
- )
336
- ),
337
- 'cached+plain' => Factbase::CachedFactbase.new(Factbase.new(maps)),
338
- 'cached+logged+plain' => Factbase::CachedFactbase.new(
339
- Factbase::Logged.new(
340
- Factbase.new(maps),
341
- Loog::NULL
342
- )
343
- ),
344
- 'logged+plain' => Factbase::Logged.new(Factbase.new(maps), Loog::NULL),
345
- 'indexed+cached+plain' => Factbase::IndexedFactbase.new(
346
- Factbase::CachedFactbase.new(Factbase.new(maps))
347
- ),
348
- 'indexed+cached+rules+plain' => Factbase::IndexedFactbase.new(
349
- Factbase::CachedFactbase.new(
350
- Factbase::Rules.new(
351
- Factbase.new(maps),
352
- '(always)',
353
- uid: '_id'
354
- )
355
- )
356
- ),
357
- 'cached+indexed+plain' => Factbase::CachedFactbase.new(
358
- Factbase::IndexedFactbase.new(Factbase.new(maps))
359
- ),
360
- 'indexed+indexed+plain' => Factbase::IndexedFactbase.new(
361
- Factbase::IndexedFactbase.new(Factbase.new(maps))
362
- ),
363
- 'cached+cached+cached+plain' => Factbase::CachedFactbase.new(
364
- Factbase::CachedFactbase.new(
365
- Factbase::CachedFactbase.new(
366
- Factbase.new(maps)
367
- )
368
- )
369
- ),
370
- 'tallied+pre+rules+inv+plain' => Factbase::Tallied.new(
371
- Factbase::Pre.new(
372
- Factbase::Rules.new(
373
- Factbase::Inv.new(
374
- Factbase.new(maps)
375
- ) { nil },
376
- '(always)'
377
- )
378
- ) { nil }
379
- ),
380
- 'sync+cached+indexed+plain' => Factbase::SyncFactbase.new(
381
- Factbase::CachedFactbase.new(
382
- Factbase::IndexedFactbase.new(
383
- Factbase.new(maps)
384
- )
385
- )
386
- ),
387
- 'sync+logged+plain' => Factbase::SyncFactbase.new(
388
- Factbase::Logged.new(
389
- Factbase.new(maps),
390
- Loog::NULL
391
- )
392
- ),
393
- 'sync+cached+indexed+logged+plain' => Factbase::SyncFactbase.new(
394
- Factbase::CachedFactbase.new(
395
- Factbase::IndexedFactbase.new(
396
- Factbase::Logged.new(
397
- Factbase.new(maps),
398
- Loog::NULL
399
- )
400
- )
401
- )
402
- ),
403
- 'sync+sync+sync+plain' => Factbase::SyncFactbase.new(
404
- Factbase::SyncFactbase.new(
405
- Factbase::SyncFactbase.new(
406
- Factbase::SyncFactbase.new(
407
- Factbase.new(maps)
408
- )
409
- )
410
- )
411
- ),
412
- 'logged+logged+logged+plain' => Factbase::SyncFactbase.new(
413
- Factbase::Logged.new(
414
- Factbase::Logged.new(
415
- Factbase::Logged.new(
416
- Factbase.new(maps),
417
- Loog::NULL
418
- ),
419
- Loog::NULL
420
- ),
421
- Loog::NULL
422
- )
423
- ),
424
- 'all+plain' => Factbase::Tallied.new(
425
- Factbase::Pre.new(
426
- Factbase::Rules.new(
427
- Factbase::Inv.new(
428
- Factbase::SyncFactbase.new(
429
- Factbase::CachedFactbase.new(
430
- Factbase::IndexedFactbase.new(
431
- Factbase::Logged.new(
432
- Factbase.new(maps),
433
- Loog::NULL
434
- )
435
- )
436
- )
437
- )
438
- ) { nil },
439
- '(always)'
440
- )
441
- ) { nil }
442
- )
443
- }.each(&)
444
- end
445
- end
@@ -1,128 +0,0 @@
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/rules'
9
- require_relative '../../lib/factbase/pre'
10
-
11
- # Test.
12
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
13
- # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
14
- # License:: MIT
15
- class TestRules < Factbase::Test
16
- def test_simple_checking
17
- fb = Factbase::Rules.new(
18
- Factbase.new,
19
- '(when (exists first) (exists second))'
20
- )
21
- f1 = fb.insert
22
- f1.second = 2
23
- f1.first = 1
24
- f2 = fb.insert
25
- assert_raises(StandardError) do
26
- f2.first = 1
27
- end
28
- end
29
-
30
- def test_check_with_id
31
- fb = Factbase::Rules.new(Factbase.new, '(exists foo)', uid: 'id')
32
- fb.txn do |fbt|
33
- f = fbt.insert
34
- f.foo = 42
35
- end
36
- end
37
-
38
- def test_to_string
39
- fb = Factbase::Rules.new(
40
- Factbase.new,
41
- '(when (exists a) (exists b))'
42
- )
43
- f = fb.insert
44
- f.foo = 42
45
- s = f.to_s
46
- assert_predicate(s.length, :positive?, s)
47
- assert_equal('[ foo: [42] ]', s)
48
- end
49
-
50
- def test_query_one
51
- fb = Factbase::Rules.new(Factbase.new, '(always)')
52
- f = fb.insert
53
- f.foo = 42
54
- assert_equal(1, fb.query('(agg (eq foo $v) (count))').one(Factbase.new, v: 42))
55
- end
56
-
57
- def test_check_only_when_txn_is_closed
58
- fb = Factbase::Rules.new(Factbase.new, '(when (exists a) (exists b))')
59
- ok = false
60
- assert_raises(StandardError) do
61
- fb.txn do |fbt|
62
- f = fbt.insert
63
- f.a = 1
64
- f.c = 2
65
- ok = true
66
- end
67
- end
68
- assert(ok)
69
- end
70
-
71
- def test_rollback_on_violation
72
- fb = Factbase::Rules.new(Factbase.new, '(when (exists a) (exists b))')
73
- assert_raises(StandardError) do
74
- fb.txn do |fbt|
75
- f = fbt.insert
76
- f.a = 1
77
- f.c = 2
78
- end
79
- end
80
- assert_equal(0, fb.size)
81
- end
82
-
83
- def test_defends_against_id_duplicates
84
- fb = Factbase::Rules.new(Factbase.new, '(always)', uid: 'id')
85
- assert_raises(StandardError) do
86
- fb.txn do |fbt|
87
- fbt.insert.then do |f|
88
- f.id = 1
89
- f.id = 2
90
- end
91
- end
92
- end
93
- end
94
-
95
- def test_fails_if_id_duplicates_are_there_already
96
- fb = Factbase::Rules.new(Factbase.new, '(always)', uid: 'id')
97
- fb.insert.then do |f|
98
- f.id = 1
99
- f.id = 2
100
- end
101
- assert_raises(StandardError) do
102
- fb.txn do |fbt|
103
- fbt.insert.then do |f|
104
- f.foo = 42
105
- end
106
- end
107
- end
108
- end
109
-
110
- def test_in_combination_with_pre
111
- fb = Factbase::Rules.new(Factbase.new, '(when (exists a) (exists b))')
112
- fb =
113
- Factbase::Pre.new(fb) do |f|
114
- f.hello = 42
115
- end
116
- ok = false
117
- assert_raises(StandardError) do
118
- fb.txn do |fbt|
119
- f = fbt.insert
120
- f.a = 1
121
- f.c = 2
122
- ok = true
123
- end
124
- end
125
- assert(ok)
126
- assert_equal(0, fb.query('(eq hello $v)').each(v: 42).to_a.size)
127
- end
128
- end