factbase 0.7.5 → 0.9.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +3 -3
  5. data/README.md +24 -27
  6. data/REUSE.toml +7 -2
  7. data/Rakefile +8 -1
  8. data/benchmark/bench_factbase.rb +1 -1
  9. data/fixtures/stories/agg.yml +17 -0
  10. data/fixtures/stories/always.yml +16 -0
  11. data/fixtures/stories/as.yml +16 -0
  12. data/fixtures/stories/count.yml +18 -0
  13. data/fixtures/stories/eq.yml +30 -0
  14. data/fixtures/stories/gt.yml +18 -0
  15. data/fixtures/stories/join.yml +19 -0
  16. data/fixtures/stories/max.yml +14 -0
  17. data/fixtures/stories/min.yml +14 -0
  18. data/fixtures/stories/nth.yml +14 -0
  19. data/fixtures/stories/or.yml +18 -0
  20. data/fixtures/stories/sprintf.yml +12 -0
  21. data/fixtures/stories/sum.yml +14 -0
  22. data/lib/factbase/cached/cached_fact.rb +28 -0
  23. data/lib/factbase/cached/cached_factbase.rb +64 -0
  24. data/lib/factbase/cached/cached_query.rb +61 -0
  25. data/lib/factbase/cached/cached_term.rb +25 -0
  26. data/lib/factbase/fact.rb +13 -13
  27. data/lib/factbase/indexed/indexed_fact.rb +28 -0
  28. data/lib/factbase/indexed/indexed_factbase.rb +64 -0
  29. data/lib/factbase/indexed/indexed_query.rb +56 -0
  30. data/lib/factbase/indexed/indexed_term.rb +60 -0
  31. data/lib/factbase/light.rb +7 -6
  32. data/lib/factbase/logged.rb +70 -35
  33. data/lib/factbase/query.rb +29 -34
  34. data/lib/factbase/rules.rb +15 -14
  35. data/lib/factbase/sync/sync_factbase.rb +57 -0
  36. data/lib/factbase/sync/sync_query.rb +61 -0
  37. data/lib/factbase/syntax.rb +16 -26
  38. data/lib/factbase/tallied.rb +10 -9
  39. data/lib/factbase/taped.rb +8 -0
  40. data/lib/factbase/tee.rb +2 -0
  41. data/lib/factbase/term.rb +45 -17
  42. data/lib/factbase/terms/aggregates.rb +17 -15
  43. data/lib/factbase/terms/aliases.rb +4 -4
  44. data/lib/factbase/terms/casting.rb +8 -8
  45. data/lib/factbase/terms/debug.rb +2 -2
  46. data/lib/factbase/terms/defn.rb +3 -3
  47. data/lib/factbase/terms/logical.rb +53 -14
  48. data/lib/factbase/terms/math.rb +26 -26
  49. data/lib/factbase/terms/meta.rb +14 -14
  50. data/lib/factbase/terms/ordering.rb +4 -4
  51. data/lib/factbase/terms/strings.rb +8 -8
  52. data/lib/factbase/terms/system.rb +3 -3
  53. data/lib/factbase.rb +67 -55
  54. data/test/factbase/cached/test_cached_factbase.rb +22 -0
  55. data/test/factbase/cached/test_cached_query.rb +79 -0
  56. data/test/factbase/indexed/test_indexed_query.rb +175 -0
  57. data/test/factbase/sync/test_sync_query.rb +30 -0
  58. data/test/factbase/terms/test_aggregates.rb +5 -5
  59. data/test/factbase/terms/test_aliases.rb +7 -7
  60. data/test/factbase/terms/test_casting.rb +8 -8
  61. data/test/factbase/terms/test_debug.rb +6 -6
  62. data/test/factbase/terms/test_defn.rb +14 -14
  63. data/test/factbase/terms/test_logical.rb +17 -19
  64. data/test/factbase/terms/test_math.rb +63 -61
  65. data/test/factbase/terms/test_meta.rb +36 -36
  66. data/test/factbase/terms/test_ordering.rb +9 -9
  67. data/test/factbase/terms/test_strings.rb +10 -10
  68. data/test/factbase/terms/test_system.rb +6 -6
  69. data/test/factbase/test_accum.rb +5 -5
  70. data/test/factbase/test_fact.rb +12 -12
  71. data/test/factbase/test_logged.rb +7 -0
  72. data/test/factbase/test_query.rb +99 -37
  73. data/test/factbase/test_rules.rb +1 -1
  74. data/test/factbase/test_syntax.rb +24 -11
  75. data/test/factbase/test_tee.rb +8 -8
  76. data/test/factbase/test_term.rb +39 -30
  77. data/test/test__helper.rb +2 -2
  78. data/test/test_factbase.rb +6 -0
  79. metadata +29 -4
  80. data/lib/factbase/query_once.rb +0 -54
  81. data/lib/factbase/term_once.rb +0 -67
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a3adc3cc2aec38609bd1d98b4266e18db6d3fc9e0e9eb3714f9772bf6bfee60
4
- data.tar.gz: 7eae0cf3bbd8b1d432971f051ad08be1f39df640245d2b0f565c14a95a436aa1
3
+ metadata.gz: d9aeae89fbcf5e3c1b74f475a8ad001867869d29eab8b861a26b07a2fee9db93
4
+ data.tar.gz: 0c20ed8b499cb495ca438621b30c24c9c837a73c8cffdde812ecacabc7b9a49c
5
5
  SHA512:
6
- metadata.gz: ee55ea6c2b5c2bd780c5b241bc16c14a2151adcb44829ec15b71c6e6fa614f9c9776e11d7c0bf76e2693a9141c745a0a233946e7b09b9961dc042f2ab22d412a
7
- data.tar.gz: 59c44e45b9a6f2bbfe70a7fe1e649fba6c3af8b9d04e9d225289cd32d7736db1e37a138f53ed300b5639fdadbe9cef448750c9033c08aad10f4d5e13c0587e91
6
+ metadata.gz: 5f342a46704cdfa9f4355726fd7ca98f6a20d44090fd84c70e69189167abb97c378aca6f52fb295e736acbe4f501d61c65dd93ae833338327be8e81c9bb8f9a7
7
+ data.tar.gz: 0ec8785757ba4a370e0b1f50b3a7d50271e5245278b23d1b6a45618c90676517bf45763af7f5b150c606c22571992fcd15a95e771e661d2f62308add8e97d93a
data/.rubocop.yml CHANGED
@@ -27,7 +27,7 @@ Layout/MultilineMethodCallIndentation:
27
27
  Metrics/AbcSize:
28
28
  Enabled: false
29
29
  Metrics/BlockLength:
30
- Max: 30
30
+ Max: 50
31
31
  Metrics/CyclomaticComplexity:
32
32
  Max: 25
33
33
  Metrics/PerceivedComplexity:
data/Gemfile CHANGED
@@ -10,7 +10,7 @@ gem 'minitest', '5.25.4', require: false
10
10
  gem 'minitest-reporters', '1.7.1', require: false
11
11
  gem 'rake', '13.2.1', require: false
12
12
  gem 'rspec-rails', '7.1.1', require: false
13
- gem 'rubocop', '1.73.1', require: false
13
+ gem 'rubocop', '1.73.2', require: false
14
14
  gem 'rubocop-minitest', '>0', require: false
15
15
  gem 'rubocop-performance', '>0', require: false
16
16
  gem 'rubocop-rake', '>0', require: false
data/Gemfile.lock CHANGED
@@ -100,7 +100,7 @@ GEM
100
100
  date
101
101
  stringio
102
102
  racc (1.8.1)
103
- rack (3.1.10)
103
+ rack (3.1.11)
104
104
  rack-session (2.1.0)
105
105
  base64 (>= 0.1.0)
106
106
  rack (>= 3.0.0)
@@ -148,7 +148,7 @@ GEM
148
148
  rspec-mocks (~> 3.13)
149
149
  rspec-support (~> 3.13)
150
150
  rspec-support (3.13.2)
151
- rubocop (1.73.1)
151
+ rubocop (1.73.2)
152
152
  json (~> 2.3)
153
153
  language_server-protocol (~> 3.17.0.2)
154
154
  lint_roller (~> 1.1.0)
@@ -218,7 +218,7 @@ DEPENDENCIES
218
218
  minitest-reporters (= 1.7.1)
219
219
  rake (= 13.2.1)
220
220
  rspec-rails (= 7.1.1)
221
- rubocop (= 1.73.1)
221
+ rubocop (= 1.73.2)
222
222
  rubocop-minitest (> 0)
223
223
  rubocop-performance (> 0)
224
224
  rubocop-rake (> 0)
data/README.md CHANGED
@@ -208,37 +208,34 @@ This is the result of the benchmark:
208
208
 
209
209
  <!-- benchmark_begin -->
210
210
  ```text
211
-
212
-
213
- Benchmarking, please wait a few seconds...
214
211
  user system total real
215
- insert 500 facts 0.017487 0.000000 0.017487 ( 0.017560)
216
- export 500 facts 0.000517 0.000024 0.000541 ( 0.000542)
217
- import 10456 bytes (500 facts) 0.000404 0.000000 0.000404 ( 0.000404)
218
- insert 10 facts 0.001825 0.000000 0.001825 ( 0.001826)
219
- query 10 times w/txn 0.049619 0.003999 0.053618 ( 0.053621)
220
- query 10 times w/o txn 0.043671 0.000000 0.043671 ( 0.043673)
221
- modify 10 attrs w/txn 0.030897 0.000975 0.031872 ( 0.031875)
222
- delete 10 facts w/txn 0.012024 0.000013 0.012037 ( 0.012038)
223
- (and (eq what 'issue-was-closed') (exists... -> 200 7.836740 0.018246 7.854986 ( 7.855478)
224
- (and (eq what 'issue-was-closed') (exists... -> 200/txn 8.817858 0.014003 8.831861 ( 8.832345)
225
- (and (eq what 'issue-was-closed') (exists... -> zero 10.051354 0.017001 10.068355 ( 10.069083)
226
- (and (eq what 'issue-was-closed') (exists... -> zero/txn 10.667497 0.016002 10.683499 ( 10.684435)
227
- (gt time '2024-03-23T03:21:43Z') 0.008827 0.000000 0.008827 ( 0.008827)
228
- (gt cost 50) 0.009784 0.000002 0.009786 ( 0.009789)
229
- (eq title 'Object Thinking 5000') 0.008519 0.000000 0.008519 ( 0.008523)
230
- (and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.013591 0.000000 0.013591 ( 0.013595)
231
- (eq id (agg (always) (max id))) 0.021419 0.000000 0.021419 ( 0.021423)
232
- (join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 0.141201 0.000000 0.141201 ( 0.141216)
233
- delete! 0.004025 0.000000 0.004025 ( 0.004027)
234
- Taped.append() x50000 0.038128 0.001997 0.040125 ( 0.040130)
235
- Taped.each() x125 1.510154 0.001001 1.511155 ( 1.511313)
236
- Taped.delete_if() x375 0.883123 0.000000 0.883123 ( 0.883229)
212
+ insert 20000 facts 0.561512 0.006100 0.567612 ( 0.567669)
213
+ export 20000 facts 0.021207 0.001926 0.023133 ( 0.023136)
214
+ import 411017 bytes (20000 facts) 0.030268 0.004025 0.034293 ( 0.034298)
215
+ insert 10 facts 0.041799 0.003981 0.045780 ( 0.045783)
216
+ query 10 times w/txn 1.949384 0.022040 1.971424 ( 1.971572)
217
+ query 10 times w/o txn 0.766215 0.000000 0.766215 ( 0.766298)
218
+ modify 10 attrs w/txn 1.831320 0.019013 1.850333 ( 1.850663)
219
+ delete 10 facts w/txn 0.720217 0.000982 0.721199 ( 0.721265)
220
+ (and (eq what 'issue-was-closed') (exists... -> 200 2.699199 0.003986 2.703185 ( 2.703395)
221
+ (and (eq what 'issue-was-closed') (exists... -> 200/txn 2.711764 0.003998 2.715762 ( 2.716000)
222
+ (and (eq what 'issue-was-closed') (exists... -> zero 4.091858 0.003001 4.094859 ( 4.095101)
223
+ (and (eq what 'issue-was-closed') (exists... -> zero/txn 4.156396 0.003004 4.159400 ( 4.159589)
224
+ (gt time '2024-03-23T03:21:43Z') 0.104266 0.001002 0.105268 ( 0.105284)
225
+ (gt cost 50) 0.090716 0.000000 0.090716 ( 0.090721)
226
+ (eq title 'Object Thinking 5000') 0.005533 0.000002 0.005535 ( 0.005536)
227
+ (and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.053251 0.000002 0.053253 ( 0.053255)
228
+ (eq id (agg (always) (max id))) 0.300388 0.000000 0.300388 ( 0.300414)
229
+ (join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 1.254507 0.002999 1.257506 ( 1.257555)
230
+ delete! 0.051979 0.000000 0.051979 ( 0.051982)
231
+ Taped.append() x50000 0.028540 0.000000 0.028540 ( 0.028543)
232
+ Taped.each() x125 1.332618 0.000000 1.332618 ( 1.332645)
233
+ Taped.delete_if() x375 0.815309 0.000000 0.815309 ( 0.815348)
237
234
  ```
238
235
 
239
236
  The results were calculated in [this GHA job][benchmark-gha]
240
- on 2025-03-03 at 15:04,
237
+ on 2025-03-12 at 08:03,
241
238
  on Linux with 4 CPUs.
242
239
  <!-- benchmark_end -->
243
240
 
244
- [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13633891920
241
+ [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13806425302
data/REUSE.toml CHANGED
@@ -4,6 +4,9 @@
4
4
  version = 1
5
5
  [[annotations]]
6
6
  path = [
7
+ "**.json",
8
+ "**.md",
9
+ "**.txt",
7
10
  "**/*.csv",
8
11
  "**/*.jpg",
9
12
  "**/*.json",
@@ -13,13 +16,15 @@ path = [
13
16
  "**/*.svg",
14
17
  "**/*.txt",
15
18
  "**/*.vm",
19
+ "**/.DS_Store",
16
20
  "**/.gitignore",
21
+ "**/.pdd",
17
22
  "**/CNAME",
23
+ "**/Gemfile.lock",
24
+ ".DS_Store",
18
25
  ".gitattributes",
19
26
  ".gitignore",
20
- ".gitleaksignore",
21
27
  ".pdd",
22
- ".xcop",
23
28
  "Gemfile.lock",
24
29
  "README.md",
25
30
  "renovate.json",
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ def version
15
15
  Gem::Specification.load(Dir['*.gemspec'].first).version
16
16
  end
17
17
 
18
- task default: %i[clean test rubocop yard benchmark]
18
+ task default: %i[clean test rubocop yard]
19
19
 
20
20
  require 'rake/testtask'
21
21
  desc 'Run all unit tests'
@@ -42,7 +42,14 @@ end
42
42
 
43
43
  desc 'Benchmark them all'
44
44
  task :benchmark do
45
+ require_relative 'lib/factbase'
45
46
  fb = Factbase.new
47
+ require_relative 'lib/factbase/cached/cached_factbase'
48
+ fb = Factbase::CachedFactbase.new(fb)
49
+ require_relative 'lib/factbase/indexed/indexed_factbase'
50
+ fb = Factbase::IndexedFactbase.new(fb)
51
+ require_relative 'lib/factbase/sync/sync_factbase'
52
+ fb = Factbase::SyncFactbase.new(fb)
46
53
  require 'benchmark'
47
54
  Benchmark.bm(60) do |b|
48
55
  Dir['benchmark/bench_*.rb'].each do |f|
@@ -6,7 +6,7 @@
6
6
  require_relative '../lib/factbase'
7
7
 
8
8
  def bench_factbase(bmk, fb)
9
- total = 500
9
+ total = 20_000
10
10
  bmk.report("insert #{total} facts") do
11
11
  total.times do
12
12
  fact = fb.insert
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ facts:
5
+ - name: Jeff
6
+ dob: 1979
7
+ - name: Walter
8
+ dob: 1976
9
+ - name: Maude
10
+ dob: 1983
11
+ queries:
12
+ - query: (agg (lt dob 1980) (max dob))
13
+ one: 1979
14
+ - query: (agg (always) (min dob))
15
+ one: 1976
16
+ - query: (agg (and (eq name "Jeff") (lt dob 2025)) (min dob))
17
+ one: 1979
@@ -0,0 +1,16 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff Lebowski
7
+ age: 41
8
+ - name: Walter Sobchak
9
+ age: 45
10
+ - name: Maude Lebowski
11
+ age: 31
12
+ queries:
13
+ - query: (always)
14
+ size: 3
15
+ - query: (not (always))
16
+ size: 0
@@ -0,0 +1,16 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff
7
+ dob: 1979
8
+ - name: Walter
9
+ dob: 1976
10
+ - name: Maude
11
+ dob: 1983
12
+ queries:
13
+ - query: (as age (minus 2025 dob))
14
+ size: 3
15
+ - query: (as gender "m/f")
16
+ size: 3
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff
7
+ age: 41
8
+ - name: Walter
9
+ age: 45
10
+ - name: Maude
11
+ age: 31
12
+ queries:
13
+ - query: (count)
14
+ one: 3
15
+ - query: (agg (eq name "Jeff") (count))
16
+ one: 1
17
+ - query: (agg (gt age 40) (count))
18
+ one: 2
@@ -0,0 +1,30 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - foo: 42
7
+ bar: 33
8
+ - name: Jeff Lebowski
9
+ age: 41
10
+ - name: Maude
11
+ friends: [Jeff, Donny]
12
+ queries:
13
+ - query: (eq foo 42)
14
+ size: 1
15
+ - query: (not (eq foo 42))
16
+ size: 2
17
+ - query: (eq foo "42")
18
+ size: 0
19
+ - query: (not (eq foo "42"))
20
+ size: 3
21
+ - query: (eq foo 10)
22
+ size: 0
23
+ - query: (eq friends "Jeff")
24
+ size: 1
25
+ - query: (eq friends "Donny")
26
+ size: 1
27
+ - query: (eq name "Jeff Lebowski")
28
+ size: 1
29
+ - query: (eq bar "Hello")
30
+ size: 0
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff Lebowski
7
+ age: 41
8
+ - name: Walter Sobchak
9
+ age: 45
10
+ - name: Maude Lebowski
11
+ age: 31
12
+ queries:
13
+ - query: (gt age 40)
14
+ size: 2
15
+ - query: (not (gt age 40))
16
+ size: 1
17
+ - query: (and (gt age 30) (matches name "^M.*$"))
18
+ size: 1
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff
7
+ - name: Walter
8
+ - name: Maude
9
+ - person: Jeff
10
+ age: 41
11
+ - person: Walter
12
+ age: 45
13
+ queries:
14
+ - query: (and (join 'age<=age' (eq person $name)) (exists age))
15
+ size: 4
16
+ - query: (and (join 'age' (eq person $name)) (exists age))
17
+ size: 4
18
+ - query: (and (join 'x<=age' (eq age (agg (exists person) (max age)))) (eq x 45))
19
+ size: 5
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff
7
+ age: 41
8
+ - name: Walter
9
+ age: 45
10
+ - name: Maude
11
+ age: 31
12
+ queries:
13
+ - query: (max age)
14
+ one: 45
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff Lebowski
7
+ age: 41
8
+ - name: Walter Sobchak
9
+ age: 45
10
+ - name: Maude Lebowski
11
+ age: 31
12
+ queries:
13
+ - query: (min age)
14
+ one: 31
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff
7
+ cities: [LA, SF]
8
+ - name: Walter
9
+ cities: [LA, SF, NYC]
10
+ queries:
11
+ - query: (eq "NYC" (agg (always) (nth 0 cities)))
12
+ size: 0
13
+ - query: (eq "NYC" (agg (always) (nth 1 cities)))
14
+ size: 2
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: BMW
7
+ price: 5600
8
+ - name: Ford
9
+ price: 3200
10
+ owner: Jeff
11
+ - name: Toyota
12
+ price: 9800
13
+ owners: [Jeff, Walter]
14
+ queries:
15
+ - query: (or (eq price 5600) (gt price 6000))
16
+ size: 2
17
+ - query: (or (eq owner "Jeff") (eq owners "Walter"))
18
+ size: 2
@@ -0,0 +1,12 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - name: Jeff
7
+ age: 41
8
+ queries:
9
+ - query: (sprintf "Hello, %s!" "world")
10
+ one: "Hello, world!"
11
+ - query: (sprintf "Hello, %s!" (agg (eq age 41) (first name)))
12
+ one: "Hello, Jeff!"
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ ---
4
+ # yamllint disable rule:line-length
5
+ facts:
6
+ - title: Object Thinking
7
+ price: 29
8
+ - name: Clean Code
9
+ price: 27
10
+ - name: Refactorings
11
+ price: 41
12
+ queries:
13
+ - query: (sum price)
14
+ one: 97
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'others'
7
+ require_relative '../../factbase'
8
+
9
+ # A single fact in a factbase, which is sentitive to changes.
10
+ #
11
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
12
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
13
+ # License:: MIT
14
+ class Factbase::CachedFact
15
+ # Ctor.
16
+ # @param [Factbase::Fact] origin The original fact
17
+ # @param [Hash] cache Cache of queries (to clean it on attribute addition)
18
+ def initialize(origin, cache)
19
+ @origin = origin
20
+ @cache = cache
21
+ end
22
+
23
+ # When a method is missing, this method is called.
24
+ others do |*args|
25
+ @cache.clear if args[0].to_s.end_with?('=')
26
+ @origin.send(*args)
27
+ end
28
+ end
@@ -0,0 +1,64 @@
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_relative '../../factbase'
8
+ require_relative '../../factbase/syntax'
9
+ require_relative 'cached_fact'
10
+ require_relative 'cached_query'
11
+ require_relative 'cached_term'
12
+
13
+ # A factbase with a cache.
14
+ #
15
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
16
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
17
+ # License:: MIT
18
+ class Factbase::CachedFactbase
19
+ decoor(:origin)
20
+
21
+ # Constructor.
22
+ # @param [Factbase] origin Original factbase to decorate
23
+ # @param [Hash] cache Cache to use
24
+ def initialize(origin, cache = {})
25
+ raise 'Wront type of original' unless origin.respond_to?(:query)
26
+ @origin = origin
27
+ raise 'Wront type of cache' unless cache.is_a?(Hash)
28
+ @cache = cache
29
+ end
30
+
31
+ # Insert a new fact and return it.
32
+ # @return [Factbase::Fact] The fact just inserted
33
+ def insert
34
+ @cache.clear
35
+ Factbase::CachedFact.new(@origin.insert, @cache)
36
+ end
37
+
38
+ # Convert a query to a term.
39
+ # @param [String] query The query to convert
40
+ # @return [Factbase::Term] The term
41
+ def to_term(query)
42
+ t = @origin.to_term(query)
43
+ t.redress!(Factbase::CachedTerm, cache: @cache)
44
+ t
45
+ end
46
+
47
+ # Create a query capable of iterating.
48
+ # @param [String] term The term to use
49
+ # @param [Array<Hash>] maps Possible maps to use
50
+ def query(term, maps = nil)
51
+ term = to_term(term) if term.is_a?(String)
52
+ q = @origin.query(term, maps)
53
+ q = Factbase::CachedQuery.new(q, @cache, self) unless term.abstract?
54
+ q
55
+ end
56
+
57
+ # Run an ACID transaction.
58
+ # @return [Factbase::Churn] How many facts have been changed (zero if rolled back)
59
+ def txn
60
+ @origin.txn do |fbt|
61
+ yield Factbase::CachedFactbase.new(fbt, @cache)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../../factbase'
7
+
8
+ # Query with a cache, a decorator of another query.
9
+ #
10
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
11
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
12
+ # License:: MIT
13
+ class Factbase::CachedQuery
14
+ # Constructor.
15
+ # @param [Factbase::Query] origin Original query
16
+ # @param [Hash] cache The cache
17
+ def initialize(origin, cache, fb)
18
+ @origin = origin
19
+ @cache = cache
20
+ @fb = fb
21
+ end
22
+
23
+ # Print it as a string.
24
+ # @return [String] The query as a string
25
+ def to_s
26
+ @origin.to_s
27
+ end
28
+
29
+ # Iterate facts one by one.
30
+ # @param [Hash] params Optional params accessible in the query via the "$" symbol
31
+ # @yield [Fact] Facts one-by-one
32
+ # @return [Integer] Total number of facts yielded
33
+ def each(fb = @fb, params = nil)
34
+ return to_enum(__method__, fb, params) unless block_given?
35
+ key = "each #{@origin}"
36
+ before = @cache[key]
37
+ @cache[key] = @origin.each(fb).to_a if before.nil?
38
+ @cache[key].each do |f|
39
+ require_relative 'cached_fact'
40
+ yield Factbase::CachedFact.new(f, @cache)
41
+ end
42
+ end
43
+
44
+ # Read a single value.
45
+ # @param [Hash] fb The factbase
46
+ # @param [Hash] _params Optional params accessible in the query via the "$" symbol (unused)
47
+ # @return The value evaluated
48
+ def one(fb = @fb, _params = nil)
49
+ key = "one: #{@origin}"
50
+ before = @cache[key]
51
+ @cache[key] = @origin.one(fb) if before.nil?
52
+ @cache[key]
53
+ end
54
+
55
+ # Delete all facts that match the query.
56
+ # @return [Integer] Total number of facts deleted
57
+ def delete!(fb = @fb)
58
+ @cache.clear
59
+ @origin.delete!(fb)
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../../factbase'
7
+
8
+ # Term with a cache.
9
+ #
10
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
11
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
12
+ # License:: MIT
13
+ module Factbase::CachedTerm
14
+ # Does it match the fact?
15
+ # @param [Factbase::Fact] fact The fact
16
+ # @param [Array<Factbase::Fact>] maps All maps available
17
+ # @return [bool] TRUE if matches
18
+ def evaluate(fact, maps, fb)
19
+ return super unless static? && !abstract?
20
+ key = [maps.object_id, to_s]
21
+ before = @cache[key]
22
+ @cache[key] = super if before.nil?
23
+ @cache[key]
24
+ end
25
+ end