factbase 0.9.5 → 0.9.6

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: '05496abcbec07a37de19ac7f1218943d1d50d1f0b3611ab48e6b8489675d274c'
4
- data.tar.gz: b8f33d58069745e5405d6f1714c80f6fb3badc437333ce1fe685af5fb245bc42
3
+ metadata.gz: ab2bcf895fa6934620163134f67815a20126515fcc163bdf7a232752a474bfcd
4
+ data.tar.gz: 2b96185e57e1ffa3176d34839940cff0a025b4cef8c7cd49b99bf9bdc79ab05c
5
5
  SHA512:
6
- metadata.gz: 1475969fc18741368eff7ababc1ff5764f36244f3e11276d21a5d4f03f9d5c3ce444cf9ecd0482be3e9f3a3b3fadbc11cba5a711244dd26b39fc4be60a6915f9
7
- data.tar.gz: 2e30a6d6cc39ada5038f98d8b6112250e527b756a0f9d5f5a8aebecd661d8de8e022d4d5659065ec0273035fbe740f5ea655b3f82e701993084a6755c96700f4
6
+ metadata.gz: ac68811b7e3fd879a0a19079c537d5718095e0f8786de51b9fa949a1541f518dbd0f93ea9aedd749b41e90c965a9f81ec1fdd75b24e1959c0adc818d899f8017
7
+ data.tar.gz: dd39f0a82dc183b2c324660f1a9bb3ba6bf95cb2fa37c528d2684197aa96167dc62d6ee4797efd20c705a8c530c6e8ae09f332954023b1791c4dcb806f7b9a54
data/.rubocop.yml CHANGED
@@ -20,6 +20,8 @@ Gemspec/RequiredRubyVersion:
20
20
  Enabled: false
21
21
  Metrics/MethodLength:
22
22
  Enabled: false
23
+ Metrics/ModuleLength:
24
+ Enabled: false
23
25
  Style/ClassAndModuleChildren:
24
26
  Enabled: false
25
27
  Layout/MultilineMethodCallIndentation:
@@ -29,9 +31,9 @@ Metrics/AbcSize:
29
31
  Metrics/BlockLength:
30
32
  Max: 50
31
33
  Metrics/CyclomaticComplexity:
32
- Max: 40
34
+ Max: 100
33
35
  Metrics/PerceivedComplexity:
34
- Max: 50
36
+ Max: 100
35
37
  Metrics/ClassLength:
36
38
  Enabled: false
37
39
  Layout/EmptyLineAfterGuardClause:
data/.rultor.yml CHANGED
@@ -12,6 +12,7 @@ install: |
12
12
  release:
13
13
  pre: false
14
14
  script: |-
15
+ set -x
15
16
  [[ "${tag}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit -1
16
17
  bundle exec rake
17
18
  rm -rf *.gem
data/Gemfile CHANGED
@@ -6,18 +6,18 @@
6
6
  source 'https://rubygems.org'
7
7
  gemspec
8
8
 
9
- gem 'minitest', '5.25.5', require: false
10
- gem 'minitest-reporters', '1.7.1', require: false
9
+ gem 'minitest', '~>5.25', require: false
10
+ gem 'minitest-reporters', '~>1.7', require: false
11
11
  gem 'os', '>0', require: false
12
12
  gem 'qbash', '>0', require: false
13
- gem 'rake', '13.2.1', require: false
14
- gem 'rspec-rails', '7.1.1', require: false
15
- gem 'rubocop', '1.74.0', require: false
13
+ gem 'rake', '~>13.2', require: false
14
+ gem 'rdoc', '~>6.13', require: false
15
+ gem 'rubocop', '~>1.74', require: false
16
16
  gem 'rubocop-minitest', '>0', require: false
17
17
  gem 'rubocop-performance', '>0', require: false
18
18
  gem 'rubocop-rake', '>0', require: false
19
19
  gem 'rubocop-rspec', '>0', require: false
20
- gem 'simplecov', '0.22.0', require: false
21
- gem 'simplecov-cobertura', '2.1.0', require: false
22
- gem 'threads', '0.4.1', require: false
23
- gem 'yard', '0.9.37', require: false
20
+ gem 'simplecov', '~>0.22', require: false
21
+ gem 'simplecov-cobertura', '~>2.1', require: false
22
+ gem 'threads', '~>0.4', require: false
23
+ gem 'yard', '~>0.9', require: false
data/Gemfile.lock CHANGED
@@ -2,80 +2,34 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  factbase (0.0.0)
5
- backtrace (>= 0.4.0)
6
- decoor (>= 0.0.1)
5
+ backtrace (~> 0.4)
6
+ decoor (~> 0.0)
7
7
  json (~> 2.7)
8
- loog (>= 0.6.0)
8
+ logger (~> 1.0)
9
+ loog (~> 0.6)
9
10
  nokogiri (~> 1.10)
10
- others (>= 0.0.3)
11
- tago (>= 0.0.2)
11
+ others (~> 0.0)
12
+ tago (~> 0.0)
12
13
  yaml (~> 0.3)
13
14
 
14
15
  GEM
15
16
  remote: https://rubygems.org/
16
17
  specs:
17
- actionpack (8.0.2)
18
- actionview (= 8.0.2)
19
- activesupport (= 8.0.2)
20
- nokogiri (>= 1.8.5)
21
- rack (>= 2.2.4)
22
- rack-session (>= 1.0.1)
23
- rack-test (>= 0.6.3)
24
- rails-dom-testing (~> 2.2)
25
- rails-html-sanitizer (~> 1.6)
26
- useragent (~> 0.16)
27
- actionview (8.0.2)
28
- activesupport (= 8.0.2)
29
- builder (~> 3.1)
30
- erubi (~> 1.11)
31
- rails-dom-testing (~> 2.2)
32
- rails-html-sanitizer (~> 1.6)
33
- activesupport (8.0.2)
34
- base64
35
- benchmark (>= 0.3)
36
- bigdecimal
37
- concurrent-ruby (~> 1.0, >= 1.3.1)
38
- connection_pool (>= 2.2.5)
39
- drb
40
- i18n (>= 1.6, < 2)
41
- logger (>= 1.4.2)
42
- minitest (>= 5.1)
43
- securerandom (>= 0.3)
44
- tzinfo (~> 2.0, >= 2.0.5)
45
- uri (>= 0.13.1)
46
18
  ansi (1.5.0)
47
- ast (2.4.2)
19
+ ast (2.4.3)
48
20
  backtrace (0.4.0)
49
- base64 (0.2.0)
50
- benchmark (0.4.0)
51
- bigdecimal (3.1.9)
52
21
  builder (3.3.0)
53
22
  concurrent-ruby (1.3.5)
54
- connection_pool (2.5.0)
55
- crass (1.0.6)
56
23
  date (3.4.1)
57
24
  decoor (0.0.1)
58
- diff-lcs (1.6.0)
59
25
  docile (1.4.1)
60
- drb (2.2.1)
61
26
  elapsed (0.0.1)
62
27
  loog (> 0)
63
28
  tago (> 0)
64
- erubi (1.13.1)
65
- i18n (1.14.7)
66
- concurrent-ruby (~> 1.0)
67
- io-console (0.8.0)
68
- irb (1.15.1)
69
- pp (>= 0.6.0)
70
- rdoc (>= 4.0.0)
71
- reline (>= 0.4.2)
72
29
  json (2.10.2)
73
30
  language_server-protocol (3.17.0.4)
74
31
  lint_roller (1.1.0)
75
- logger (1.6.6)
76
- loofah (2.24.0)
77
- crass (~> 1.0.2)
78
- nokogiri (>= 1.12.0)
32
+ logger (1.7.0)
79
33
  loog (0.6.0)
80
34
  minitest (5.25.5)
81
35
  minitest-reporters (1.7.1)
@@ -83,23 +37,21 @@ GEM
83
37
  builder
84
38
  minitest (>= 5.0)
85
39
  ruby-progressbar
86
- nokogiri (1.18.4-arm64-darwin)
40
+ nokogiri (1.18.6-arm64-darwin)
87
41
  racc (~> 1.4)
88
- nokogiri (1.18.4-x64-mingw-ucrt)
42
+ nokogiri (1.18.6-x64-mingw-ucrt)
89
43
  racc (~> 1.4)
90
- nokogiri (1.18.4-x86_64-darwin)
44
+ nokogiri (1.18.6-x86_64-darwin)
91
45
  racc (~> 1.4)
92
- nokogiri (1.18.4-x86_64-linux-gnu)
46
+ nokogiri (1.18.6-x86_64-linux-gnu)
93
47
  racc (~> 1.4)
94
48
  os (1.1.4)
95
49
  others (0.0.3)
96
50
  parallel (1.26.3)
97
- parser (3.3.7.1)
51
+ parser (3.3.7.3)
98
52
  ast (~> 2.4.1)
99
53
  racc
100
- pp (0.6.2)
101
- prettyprint
102
- prettyprint (0.2.0)
54
+ prism (1.4.0)
103
55
  psych (5.2.3)
104
56
  date
105
57
  stringio
@@ -109,55 +61,13 @@ GEM
109
61
  loog (> 0)
110
62
  tago (> 0)
111
63
  racc (1.8.1)
112
- rack (3.1.12)
113
- rack-session (2.1.0)
114
- base64 (>= 0.1.0)
115
- rack (>= 3.0.0)
116
- rack-test (2.2.0)
117
- rack (>= 1.3)
118
- rackup (2.2.1)
119
- rack (>= 3)
120
- rails-dom-testing (2.2.0)
121
- activesupport (>= 5.0.0)
122
- minitest
123
- nokogiri (>= 1.6)
124
- rails-html-sanitizer (1.6.2)
125
- loofah (~> 2.21)
126
- nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
127
- railties (8.0.2)
128
- actionpack (= 8.0.2)
129
- activesupport (= 8.0.2)
130
- irb (~> 1.13)
131
- rackup (>= 1.0.0)
132
- rake (>= 12.2)
133
- thor (~> 1.0, >= 1.2.2)
134
- zeitwerk (~> 2.6)
135
64
  rainbow (3.1.1)
136
65
  rake (13.2.1)
137
- rdoc (6.12.0)
66
+ rdoc (6.13.1)
138
67
  psych (>= 4.0.0)
139
68
  regexp_parser (2.10.0)
140
- reline (0.6.0)
141
- io-console (~> 0.5)
142
69
  rexml (3.4.1)
143
- rspec-core (3.13.3)
144
- rspec-support (~> 3.13.0)
145
- rspec-expectations (3.13.3)
146
- diff-lcs (>= 1.2.0, < 2.0)
147
- rspec-support (~> 3.13.0)
148
- rspec-mocks (3.13.2)
149
- diff-lcs (>= 1.2.0, < 2.0)
150
- rspec-support (~> 3.13.0)
151
- rspec-rails (7.1.1)
152
- actionpack (>= 7.0)
153
- activesupport (>= 7.0)
154
- railties (>= 7.0)
155
- rspec-core (~> 3.13)
156
- rspec-expectations (~> 3.13)
157
- rspec-mocks (~> 3.13)
158
- rspec-support (~> 3.13)
159
- rspec-support (3.13.2)
160
- rubocop (1.74.0)
70
+ rubocop (1.75.1)
161
71
  json (~> 2.3)
162
72
  language_server-protocol (~> 3.17.0.2)
163
73
  lint_roller (~> 1.1.0)
@@ -165,11 +75,12 @@ GEM
165
75
  parser (>= 3.3.0.2)
166
76
  rainbow (>= 2.2.2, < 4.0)
167
77
  regexp_parser (>= 2.9.3, < 3.0)
168
- rubocop-ast (>= 1.38.0, < 2.0)
78
+ rubocop-ast (>= 1.43.0, < 2.0)
169
79
  ruby-progressbar (~> 1.7)
170
80
  unicode-display_width (>= 2.4.0, < 4.0)
171
- rubocop-ast (1.39.0)
172
- parser (>= 3.3.1.0)
81
+ rubocop-ast (1.43.0)
82
+ parser (>= 3.3.7.2)
83
+ prism (~> 1.4)
173
84
  rubocop-minitest (0.37.1)
174
85
  lint_roller (~> 1.1)
175
86
  rubocop (>= 1.72.1, < 2.0)
@@ -185,7 +96,6 @@ GEM
185
96
  lint_roller (~> 1.1)
186
97
  rubocop (~> 1.72, >= 1.72.1)
187
98
  ruby-progressbar (1.13.0)
188
- securerandom (0.4.1)
189
99
  simplecov (0.22.0)
190
100
  docile (~> 1.1)
191
101
  simplecov-html (~> 0.11)
@@ -195,22 +105,16 @@ GEM
195
105
  simplecov (~> 0.19)
196
106
  simplecov-html (0.13.1)
197
107
  simplecov_json_formatter (0.1.4)
198
- stringio (3.1.5)
199
- tago (0.0.2)
200
- thor (1.3.2)
108
+ stringio (3.1.6)
109
+ tago (0.1.0)
201
110
  threads (0.4.1)
202
111
  backtrace (~> 0)
203
112
  concurrent-ruby (~> 1.0)
204
- tzinfo (2.0.6)
205
- concurrent-ruby (~> 1.0)
206
113
  unicode-display_width (3.1.4)
207
114
  unicode-emoji (~> 4.0, >= 4.0.4)
208
115
  unicode-emoji (4.0.4)
209
- uri (1.0.3)
210
- useragent (0.16.11)
211
116
  yaml (0.4.0)
212
117
  yard (0.9.37)
213
- zeitwerk (2.7.2)
214
118
 
215
119
  PLATFORMS
216
120
  arm64-darwin-22
@@ -223,21 +127,21 @@ PLATFORMS
223
127
 
224
128
  DEPENDENCIES
225
129
  factbase!
226
- minitest (= 5.25.5)
227
- minitest-reporters (= 1.7.1)
130
+ minitest (~> 5.25)
131
+ minitest-reporters (~> 1.7)
228
132
  os (> 0)
229
133
  qbash (> 0)
230
- rake (= 13.2.1)
231
- rspec-rails (= 7.1.1)
232
- rubocop (= 1.74.0)
134
+ rake (~> 13.2)
135
+ rdoc (~> 6.13)
136
+ rubocop (~> 1.74)
233
137
  rubocop-minitest (> 0)
234
138
  rubocop-performance (> 0)
235
139
  rubocop-rake (> 0)
236
140
  rubocop-rspec (> 0)
237
- simplecov (= 0.22.0)
238
- simplecov-cobertura (= 2.1.0)
239
- threads (= 0.4.1)
240
- yard (= 0.9.37)
141
+ simplecov (~> 0.22)
142
+ simplecov-cobertura (~> 2.1)
143
+ threads (~> 0.4)
144
+ yard (~> 0.9)
241
145
 
242
146
  BUNDLED WITH
243
147
  2.5.16
data/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
  [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/yegor256/factbase/master/frames)
11
11
  [![Hits-of-Code](https://hitsofcode.com/github/yegor256/factbase)](https://hitsofcode.com/view/github/yegor256/factbase)
12
12
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/factbase/blob/master/LICENSE.txt)
13
+ [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyegor256%2Ffactbase.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyegor256%2Ffactbase?ref=badge_shield&issueType=license)
13
14
 
14
15
  This Ruby gem manages an in-memory database of facts.
15
16
  A fact is simply a map of properties and values.
@@ -209,33 +210,33 @@ This is the result of the benchmark:
209
210
  <!-- benchmark_begin -->
210
211
  ```text
211
212
  user system total real
212
- insert 20000 facts 0.571788 0.003758 0.575546 ( 0.575570)
213
- export 20000 facts 0.020920 0.002996 0.023916 ( 0.023920)
214
- import 410695 bytes (20000 facts) 0.027100 0.007016 0.034116 ( 0.034121)
215
- insert 10 facts 0.041616 0.004054 0.045670 ( 0.045682)
216
- query 10 times w/txn 1.631523 0.024886 1.656409 ( 1.656459)
217
- query 10 times w/o txn 0.108811 0.000990 0.109801 ( 0.109804)
218
- modify 10 attrs w/txn 1.207344 0.019000 1.226344 ( 1.226358)
219
- delete 10 facts w/txn 0.734874 0.001988 0.736862 ( 0.736890)
220
- (and (eq what 'issue-was-closed') (exists... -> 200 5.046822 0.008973 5.055795 ( 5.056175)
221
- (and (eq what 'issue-was-closed') (exists... -> 200/txn 5.006788 0.009980 5.016768 ( 5.017401)
222
- (and (eq what 'issue-was-closed') (exists... -> zero 7.793528 0.003992 7.797520 ( 7.797774)
223
- (and (eq what 'issue-was-closed') (exists... -> zero/txn 7.741986 0.001995 7.743981 ( 7.744395)
224
- (gt time '2024-03-23T03:21:43Z') 0.084850 0.000000 0.084850 ( 0.084850)
225
- (gt cost 50) 0.084610 0.001002 0.085612 ( 0.085614)
226
- (eq title 'Object Thinking 5000') 0.002526 0.000000 0.002526 ( 0.002526)
227
- (and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.163174 0.000000 0.163174 ( 0.163207)
228
- (eq id (agg (always) (max id))) 0.247507 0.001001 0.248508 ( 0.248508)
229
- (join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 0.622045 0.004995 0.627040 ( 0.627095)
230
- delete! 0.061154 0.000001 0.061155 ( 0.061156)
231
- Taped.append() x50000 0.023736 0.003999 0.027735 ( 0.027739)
232
- Taped.each() x125 1.350286 0.000002 1.350288 ( 1.350416)
233
- Taped.delete_if() x375 0.803790 0.000000 0.803790 ( 0.803852)
213
+ insert 20000 facts 0.597725 0.004012 0.601737 ( 0.601785)
214
+ export 20000 facts 0.020481 0.003039 0.023520 ( 0.023523)
215
+ import 410961 bytes (20000 facts) 0.021204 0.002017 0.023221 ( 0.023223)
216
+ insert 10 facts 0.042456 0.002961 0.045417 ( 0.045418)
217
+ query 10 times w/txn 1.594704 0.026991 1.621695 ( 1.621807)
218
+ query 10 times w/o txn 0.057308 0.000013 0.057321 ( 0.057322)
219
+ modify 10 attrs w/txn 1.225101 0.013972 1.239073 ( 1.239349)
220
+ delete 10 facts w/txn 0.735944 0.001018 0.736962 ( 0.737102)
221
+ (and (eq what 'issue-was-closed') (exists... -> 200 2.068974 0.008005 2.076979 ( 2.077134)
222
+ (and (eq what 'issue-was-closed') (exists... -> 200/txn 1.094799 0.000000 1.094799 ( 1.094849)
223
+ (and (eq what 'issue-was-closed') (exists... -> zero 2.384214 0.001004 2.385218 ( 2.385610)
224
+ (and (eq what 'issue-was-closed') (exists... -> zero/txn 1.255267 0.001916 1.257183 ( 1.257310)
225
+ (gt time '2024-03-23T03:21:43Z') 0.110972 0.002000 0.112972 ( 0.112986)
226
+ (gt cost 50) 0.087402 0.000000 0.087402 ( 0.087411)
227
+ (eq title 'Object Thinking 5000') 0.002488 0.000000 0.002488 ( 0.002488)
228
+ (and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.022121 0.000001 0.022122 ( 0.022123)
229
+ (eq id (agg (always) (max id))) 0.237595 0.000000 0.237595 ( 0.237605)
230
+ (join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 0.635953 0.003996 0.639949 ( 0.640016)
231
+ delete! 0.060744 0.000997 0.061741 ( 0.061741)
232
+ Taped.append() x50000 0.037044 0.002000 0.039044 ( 0.039048)
233
+ Taped.each() x125 1.259629 0.002997 1.262626 ( 1.262661)
234
+ Taped.delete_if() x375 0.810420 0.000005 0.810425 ( 0.810483)
234
235
  ```
235
236
 
236
237
  The results were calculated in [this GHA job][benchmark-gha]
237
- on 2025-03-18 at 12:45,
238
+ on 2025-03-30 at 05:03,
238
239
  on Linux with 4 CPUs.
239
240
  <!-- benchmark_end -->
240
241
 
241
- [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13923519605
242
+ [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/14152813171
data/Rakefile CHANGED
@@ -3,12 +3,12 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
+ require 'os'
6
7
  require 'qbash'
7
- require 'rubygems'
8
8
  require 'rake'
9
9
  require 'rake/clean'
10
+ require 'rubygems'
10
11
  require 'shellwords'
11
- require 'os'
12
12
 
13
13
  def name
14
14
  @name ||= File.basename(Dir['*.gemspec'].first, '.*')
@@ -18,7 +18,7 @@ def version
18
18
  Gem::Specification.load(Dir['*.gemspec'].first).version
19
19
  end
20
20
 
21
- task default: %i[clean test picks reqs rubocop yard]
21
+ task default: %i[clean test picks rubocop yard]
22
22
 
23
23
  require 'rake/testtask'
24
24
  desc 'Run all unit tests'
@@ -33,16 +33,10 @@ end
33
33
  desc 'Run them via Ruby, one by one'
34
34
  task :picks do
35
35
  next if OS.windows?
36
- Dir['test/**/*.rb'].each do |f|
37
- qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout)
38
- end
39
- end
40
-
41
- desc 'Require them via Ruby, one by one'
42
- task :reqs do
43
- next if OS.windows?
44
- Dir['lib/**/*.rb'].each do |f|
45
- qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout)
36
+ %w[text lib].each do |d|
37
+ Dir["#{d}/**/*.rb"].each do |f|
38
+ qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout)
39
+ end
46
40
  end
47
41
  end
48
42
 
@@ -56,7 +50,6 @@ require 'rubocop/rake_task'
56
50
  desc 'Run RuboCop on all directories'
57
51
  RuboCop::RakeTask.new(:rubocop) do |task|
58
52
  task.fail_on_error = true
59
- task.requires << 'rubocop-rspec'
60
53
  end
61
54
 
62
55
  desc 'Benchmark them all'
data/factbase.gemspec CHANGED
@@ -25,13 +25,14 @@ Gem::Specification.new do |s|
25
25
  s.files = `git ls-files`.split($RS)
26
26
  s.rdoc_options = ['--charset=UTF-8']
27
27
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
28
- s.add_dependency 'backtrace', '>=0.4.0'
29
- s.add_dependency 'decoor', '>=0.0.1'
28
+ s.add_dependency 'backtrace', '~>0.4'
29
+ s.add_dependency 'decoor', '~>0.0'
30
30
  s.add_dependency 'json', '~>2.7'
31
- s.add_dependency 'loog', '>=0.6.0'
31
+ s.add_dependency 'logger', '~>1.0'
32
+ s.add_dependency 'loog', '~>0.6'
32
33
  s.add_dependency 'nokogiri', '~>1.10'
33
- s.add_dependency 'others', '>=0.0.3'
34
- s.add_dependency 'tago', '>=0.0.2'
34
+ s.add_dependency 'others', '~>0.0'
35
+ s.add_dependency 'tago', '~>0.0'
35
36
  s.add_dependency 'yaml', '~>0.3'
36
37
  s.metadata['rubygems_mfa_required'] = 'true'
37
38
  end
@@ -43,7 +43,7 @@ class Factbase::CachedQuery
43
43
 
44
44
  # Read a single value.
45
45
  # @param [Hash] fb The factbase
46
- # @param [Hash] _params Optional params accessible in the query via the "$" symbol (unused)
46
+ # @param [Hash] params Optional params accessible in the query via the "$" symbol (unused)
47
47
  # @return The value evaluated
48
48
  def one(fb = @fb, params = {})
49
49
  key = "one: #{@origin} #{params}"
data/lib/factbase/fact.rb CHANGED
@@ -46,11 +46,6 @@ class Factbase::Fact
46
46
  @map.keys
47
47
  end
48
48
 
49
- # When a method is missing, this method is called.
50
- # Method missing handler for dynamic property access and setting
51
- # @param [Symbol] method The method name being called
52
- # @param [Array] args Method arguments
53
- # @return [Object] The value retrieved or nil if setting a value
54
49
  others do |*args|
55
50
  k = args[0].to_s
56
51
  if k.end_with?('=')
@@ -68,18 +68,45 @@ module Factbase::IndexedTerm
68
68
  end
69
69
  when :and
70
70
  r = nil
71
- @operands.each do |o|
72
- n = o.predict(maps, params)
73
- if n.nil?
74
- r = nil
75
- break
71
+ if @operands.all? { |o| o.op == :eq } && @operands.size > 1 \
72
+ && @operands.all? { |o| o.operands.first.is_a?(Symbol) && _scalar?(o.operands[1]) }
73
+ key = [maps.object_id, @operands.map { |o| o.operands.first }, :multi_eq]
74
+ props = @operands.map { |o| o.operands.first }.sort
75
+ if @idx[key].nil?
76
+ @idx[key] = {}
77
+ maps.to_a.each do |m|
78
+ _all_tuples(m, props).each do |t|
79
+ @idx[key][t] = [] if @idx[key][t].nil?
80
+ @idx[key][t].append(m)
81
+ end
82
+ end
76
83
  end
77
- if r.nil?
78
- r = n
79
- else
80
- r &= n.to_a
84
+ tuples = _as_tuples(
85
+ @operands.sort_by { |o| o.operands.first }.map do |o|
86
+ if o.operands[1].is_a?(Symbol)
87
+ sym = o.operands[1].to_s.gsub(/^\$/, '')
88
+ params[sym] || []
89
+ else
90
+ [o.operands[1]]
91
+ end
92
+ end
93
+ )
94
+ j = tuples.map { |t| @idx[key][t] || [] }.reduce(&:|)
95
+ r = (maps & []) | j
96
+ else
97
+ @operands.each do |o|
98
+ n = o.predict(maps, params)
99
+ if n.nil?
100
+ r = nil
101
+ break
102
+ end
103
+ if r.nil?
104
+ r = n
105
+ else
106
+ r &= n.to_a
107
+ end
108
+ break if r.empty?
81
109
  end
82
- break if r.empty?
83
110
  end
84
111
  r
85
112
  when :or
@@ -106,6 +133,40 @@ module Factbase::IndexedTerm
106
133
 
107
134
  private
108
135
 
136
+ # The input looks like this: [[6, 55], [3], [4, 3, 5]].
137
+ # The outputh should contain all possible combinations: [[6, 3, 4], [6, 3, 3], [55, 3, 5], ...]
138
+ def _as_tuples(values)
139
+ tuples = [values.first]
140
+ if values.size > 1
141
+ tails = _as_tuples(values[1..])
142
+ ext = []
143
+ tuples.each do |t|
144
+ tails.each do |tail|
145
+ ext << (t + tail)
146
+ end
147
+ end
148
+ tuples = ext
149
+ end
150
+ tuples
151
+ end
152
+
153
+ def _all_tuples(fact, props)
154
+ prop = props.first.to_s
155
+ tuples = []
156
+ tuples += (fact[prop] || []).zip
157
+ if props.size > 1
158
+ tails = _all_tuples(fact, props[1..])
159
+ ext = []
160
+ tuples.each do |t|
161
+ tails.each do |tail|
162
+ ext << (t + tail)
163
+ end
164
+ end
165
+ tuples = ext
166
+ end
167
+ tuples
168
+ end
169
+
109
170
  def _scalar?(item)
110
171
  item.is_a?(String) || item.is_a?(Time) || item.is_a?(Integer) || item.is_a?(Float) || item.is_a?(Symbol)
111
172
  end
@@ -131,47 +131,35 @@ class Factbase::Logged
131
131
  @fb = fb
132
132
  end
133
133
 
134
- def each(fb = nil, params = {}, &)
134
+ def to_s
135
+ @term.to_s
136
+ end
137
+
138
+ def each(fb = @fb, params = {}, &)
135
139
  return to_enum(__method__, fb, params) unless block_given?
136
140
  start = Time.now
137
141
  q = Factbase::Syntax.new(@term).to_term.to_s
138
- if block_given?
139
- r = nil
140
- tail =
141
- Factbase::Logged.elapsed do
142
- r = @fb.query(@term, @maps).each(@fb, params, &)
143
- end
144
- raise ".each of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
145
- if r.zero?
146
- @tube.say(start, "Zero/#{@fb.size} facts found by '#{q}' #{tail}")
147
- else
148
- @tube.say(start, "Found #{r}/#{@fb.size} fact(s) by '#{q}' #{tail}")
142
+ r = nil
143
+ tail =
144
+ Factbase::Logged.elapsed do
145
+ r = @fb.query(@term, @maps).each(fb, params, &)
149
146
  end
150
- r
147
+ raise ".each of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
148
+ if r.zero?
149
+ @tube.say(start, "Zero/#{@fb.size} facts found by '#{q}' #{tail}")
151
150
  else
152
- array = []
153
- tail =
154
- Factbase::Logged.elapsed do
155
- @fb.query(@term, @maps).each(@fb, params) do |f|
156
- array << f
157
- end
158
- end
159
- if array.empty?
160
- @tube.say(start, "Zero/#{@fb.size} found by '#{q}' #{tail}")
161
- else
162
- @tube.say(start, "Found #{array.size}/#{@fb.size} fact(s) by '#{q}' #{tail}")
163
- end
164
- array
151
+ @tube.say(start, "Found #{r}/#{@fb.size} fact(s) by '#{q}' #{tail}")
165
152
  end
153
+ r
166
154
  end
167
155
 
168
- def one(_fb = nil, params = {})
156
+ def one(fb = @fb, params = {})
169
157
  start = Time.now
170
158
  q = Factbase::Syntax.new(@term).to_term.to_s
171
159
  r = nil
172
160
  tail =
173
161
  Factbase::Logged.elapsed do
174
- r = @fb.query(@term, @maps).one(@fb, params)
162
+ r = @fb.query(@term, @maps).one(fb, params)
175
163
  end
176
164
  if r.nil?
177
165
  @tube.say(start, "Nothing found by '#{q}' #{tail}")
@@ -181,13 +169,13 @@ class Factbase::Logged
181
169
  r
182
170
  end
183
171
 
184
- def delete!(_fb = nil)
172
+ def delete!(fb = @fb)
185
173
  r = nil
186
174
  start = Time.now
187
175
  before = @fb.size
188
176
  tail =
189
177
  Factbase::Logged.elapsed do
190
- r = @fb.query(@term).delete!(@fb)
178
+ r = @fb.query(@term, @maps).delete!(fb)
191
179
  end
192
180
  raise ".delete! of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
193
181
  if before.zero?
@@ -41,7 +41,7 @@ class Factbase::Rules
41
41
  end
42
42
 
43
43
  def query(query, maps = nil)
44
- Query.new(@fb.query(query, maps), @check, @fb)
44
+ Query.new(@fb.query(query, maps), @check, self)
45
45
  end
46
46
 
47
47
  def txn
@@ -68,12 +68,15 @@ class Factbase::Taped
68
68
  end
69
69
 
70
70
  def &(other)
71
- return Factbase::Taped.new([], inserted: @inserted, deleted: @deleted, added: @added) if other == []
71
+ if other == [] || @origin.empty?
72
+ return Factbase::Taped.new([], inserted: @inserted, deleted: @deleted, added: @added)
73
+ end
72
74
  join(other, &:&)
73
75
  end
74
76
 
75
77
  def |(other)
76
78
  return Factbase::Taped.new(to_a, inserted: @inserted, deleted: @deleted, added: @added) if other == []
79
+ return Factbase::Taped.new(other, inserted: @inserted, deleted: @deleted, added: @added) if @origin.empty?
77
80
  join(other, &:|)
78
81
  end
79
82
 
data/lib/factbase/term.rb CHANGED
@@ -104,6 +104,7 @@ class Factbase::Term
104
104
  # should be evaluated. If no prediction can be made,
105
105
  # the same list is returned.
106
106
  # @param [Array<Hash>] maps Records to iterate, maybe
107
+ # @param [Hash] _params Params to use (keys must be strings, not symbols, with values as arrays)
107
108
  # @return [Array<Hash>] Records to iterate
108
109
  def predict(maps, _params)
109
110
  maps
data/lib/factbase.rb CHANGED
@@ -82,7 +82,7 @@ require 'yaml'
82
82
  # License:: MIT
83
83
  class Factbase
84
84
  # Current version of the gem (changed by .rultor.yml on every release)
85
- VERSION = '0.9.5' unless const_defined?(:VERSION)
85
+ VERSION = '0.9.6' unless const_defined?(:VERSION)
86
86
 
87
87
  # An exception that may be thrown in a transaction, to roll it back.
88
88
  class Rollback < StandardError; end
@@ -3,8 +3,10 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
+ require 'loog'
6
7
  require_relative '../../test__helper'
7
8
  require_relative '../../../lib/factbase'
9
+ require_relative '../../../lib/factbase/logged'
8
10
  require_relative '../../../lib/factbase/cached/cached_factbase'
9
11
  require_relative '../../../lib/factbase/indexed/indexed_factbase'
10
12
  require_relative '../../../lib/factbase/sync/sync_factbase'
@@ -58,6 +60,25 @@ class TestCachedQuery < Factbase::Test
58
60
  end
59
61
  end
60
62
 
63
+ def test_works_with_logging
64
+ fb = Factbase::CachedFactbase.new(Factbase::Logged.new(Factbase.new, Loog::NULL))
65
+ total = 10
66
+ total.times do |i|
67
+ f = fb.insert
68
+ f.foo = i
69
+ f.hello = 1
70
+ end
71
+ 3.times do
72
+ [
73
+ '(exists foo)',
74
+ '(and (gt foo -99) (exists hello))',
75
+ '(and (lt hello 1000.44) (exists foo) (not (exists bar)))'
76
+ ].each do |q|
77
+ assert_equal(total, fb.query(q).each.to_a.size)
78
+ end
79
+ end
80
+ end
81
+
61
82
  def test_caches_while_being_decorated
62
83
  fb = Factbase::SyncFactbase.new(Factbase::CachedFactbase.new(Factbase.new))
63
84
  10_000.times do |i|
@@ -59,17 +59,25 @@ class TestIndexedTerm < Factbase::Test
59
59
  :and,
60
60
  [
61
61
  Factbase::Term.new(:eq, [:foo, 42]),
62
- Factbase::Term.new(:eq, [:bar, 7])
62
+ Factbase::Term.new(:eq, %i[bar $a])
63
63
  ]
64
64
  )
65
65
  idx = {}
66
66
  term.redress!(Factbase::IndexedTerm, idx:)
67
67
  maps = Factbase::Taped.new([{ 'foo' => [42] }, { 'bar' => [7], 'foo' => [22, 42] }, { 'foo' => [22, 42] }])
68
- n = term.predict(maps, { a: 1 })
68
+ n = term.predict(maps, { 'a' => [7] })
69
69
  assert_equal(1, n.size)
70
70
  assert_kind_of(Factbase::Taped, n)
71
71
  end
72
72
 
73
+ def test_predicts_on_single_and
74
+ term = Factbase::Term.new(:and, [Factbase::Term.new(:eq, [:foo, 42])])
75
+ idx = {}
76
+ term.redress!(Factbase::IndexedTerm, idx:)
77
+ maps = Factbase::Taped.new([{ 'foo' => [42] }, { 'bar' => [7], 'foo' => [4] }])
78
+ assert_equal(1, term.predict(maps, {}).size)
79
+ end
80
+
73
81
  def test_predicts_on_or
74
82
  term = Factbase::Term.new(
75
83
  :or,
@@ -92,6 +92,12 @@ class TestLogged < Factbase::Test
92
92
  assert_equal(0, Factbase::Logged.new(fb, Loog::NULL).query('(always)').each(&:to_s))
93
93
  end
94
94
 
95
+ def test_returns_to_s_correctly
96
+ fb = Factbase.new
97
+ q = '(always)'
98
+ assert_equal(q, fb.query(q).to_s)
99
+ end
100
+
95
101
  def test_logs_when_enumerator
96
102
  fb = Factbase::Logged.new(Factbase.new, Loog::NULL)
97
103
  assert_equal(0, fb.query('(always)').each.to_a.size)
@@ -123,11 +123,13 @@ class TestQuery < Factbase::Test
123
123
  { 'num' => [42, 66, 0], 'pi' => [3.14], 'name' => ['peter'] },
124
124
  { 'num' => [0], 'time' => [Time.now - 100], 'hi' => [4], 'nome' => ['Walter'] }
125
125
  ]
126
- with_factbases(maps) do |badge, fb|
127
- queries.each do |q, r|
128
- assert_equal(r, fb.query(q).each.to_a.size, "#{q} in #{badge}")
129
- fb.txn do |fbt|
130
- assert_equal(r, fbt.query(q).each.to_a.size, "#{q} in #{badge} (txn)")
126
+ 3.times do |cycle|
127
+ with_factbases(maps) do |badge, fb|
128
+ queries.each do |q, r|
129
+ assert_equal(r, fb.query(q).each.to_a.size, "#{q} in #{badge} (cycle ##{cycle})")
130
+ fb.txn do |fbt|
131
+ assert_equal(r, fbt.query(q).each.to_a.size, "#{q} in #{badge} (txn, cycle ##{cycle})")
132
+ end
131
133
  end
132
134
  end
133
135
  end
@@ -301,12 +303,85 @@ class TestQuery < Factbase::Test
301
303
  'sync+plain' => Factbase::SyncFactbase.new(Factbase.new(maps)),
302
304
  'tallied+plain' => Factbase::Tallied.new(Factbase.new(maps)),
303
305
  'indexed+plain' => Factbase::IndexedFactbase.new(Factbase.new(maps)),
306
+ 'indexed+logged+plain' => Factbase::IndexedFactbase.new(
307
+ Factbase::Logged.new(
308
+ Factbase.new(maps),
309
+ Loog::NULL
310
+ )
311
+ ),
304
312
  'cached+plain' => Factbase::CachedFactbase.new(Factbase.new(maps)),
313
+ 'cached+logged+plain' => Factbase::CachedFactbase.new(
314
+ Factbase::Logged.new(
315
+ Factbase.new(maps),
316
+ Loog::NULL
317
+ )
318
+ ),
305
319
  'logged+plain' => Factbase::Logged.new(Factbase.new(maps), Loog::NULL),
306
320
  'indexed+cached+plain' => Factbase::IndexedFactbase.new(Factbase::CachedFactbase.new(Factbase.new(maps))),
307
321
  'cached+indexed+plain' => Factbase::CachedFactbase.new(Factbase::IndexedFactbase.new(Factbase.new(maps))),
322
+ 'indexed+indexed+plain' => Factbase::IndexedFactbase.new(
323
+ Factbase::IndexedFactbase.new(Factbase.new(maps))
324
+ ),
325
+ 'cached+cached+cached+plain' => Factbase::CachedFactbase.new(
326
+ Factbase::CachedFactbase.new(
327
+ Factbase::CachedFactbase.new(
328
+ Factbase.new(maps)
329
+ )
330
+ )
331
+ ),
332
+ 'tallied+pre+rules+inv+plain' => Factbase::Tallied.new(
333
+ Factbase::Pre.new(
334
+ Factbase::Rules.new(
335
+ Factbase::Inv.new(
336
+ Factbase.new(maps)
337
+ ) { nil },
338
+ '(always)'
339
+ )
340
+ ) { nil }
341
+ ),
308
342
  'sync+cached+indexed+plain' => Factbase::SyncFactbase.new(
309
- Factbase::CachedFactbase.new(Factbase::IndexedFactbase.new(Factbase.new(maps)))
343
+ Factbase::CachedFactbase.new(
344
+ Factbase::IndexedFactbase.new(
345
+ Factbase.new(maps)
346
+ )
347
+ )
348
+ ),
349
+ 'sync+logged+plain' => Factbase::SyncFactbase.new(
350
+ Factbase::Logged.new(
351
+ Factbase.new(maps),
352
+ Loog::NULL
353
+ )
354
+ ),
355
+ 'sync+cached+indexed+logged+plain' => Factbase::SyncFactbase.new(
356
+ Factbase::CachedFactbase.new(
357
+ Factbase::IndexedFactbase.new(
358
+ Factbase::Logged.new(
359
+ Factbase.new(maps),
360
+ Loog::NULL
361
+ )
362
+ )
363
+ )
364
+ ),
365
+ 'sync+sync+sync+plain' => Factbase::SyncFactbase.new(
366
+ Factbase::SyncFactbase.new(
367
+ Factbase::SyncFactbase.new(
368
+ Factbase::SyncFactbase.new(
369
+ Factbase.new(maps)
370
+ )
371
+ )
372
+ )
373
+ ),
374
+ 'logged+logged+logged+plain' => Factbase::SyncFactbase.new(
375
+ Factbase::Logged.new(
376
+ Factbase::Logged.new(
377
+ Factbase::Logged.new(
378
+ Factbase.new(maps),
379
+ Loog::NULL
380
+ ),
381
+ Loog::NULL
382
+ ),
383
+ Loog::NULL
384
+ )
310
385
  ),
311
386
  'all+plain' => Factbase::Tallied.new(
312
387
  Factbase::Pre.new(
@@ -18,6 +18,18 @@ class TestTaped < Factbase::Test
18
18
  assert_equal(1, t.inserted.size)
19
19
  end
20
20
 
21
+ def test_joins_with_empty
22
+ t = Factbase::Taped.new([{ foo: 'yes' }])
23
+ t &= []
24
+ assert_equal(0, t.size)
25
+ end
26
+
27
+ def test_disjoins_with_empty
28
+ t = Factbase::Taped.new([{ bar: 'oops' }])
29
+ t |= []
30
+ assert_equal(1, t.size)
31
+ end
32
+
21
33
  def test_tracks_deletion
22
34
  t = Factbase::Taped.new([{ x: 1 }, { x: 2 }])
23
35
  t.delete_if { |m| m[:x] == 1 }
@@ -81,5 +81,7 @@ class TestTerm < Factbase::Test
81
81
  t.redress!(Factbase::IndexedTerm)
82
82
  require_relative '../../lib/factbase/cached/cached_term'
83
83
  t.redress!(Factbase::CachedTerm)
84
+ assert_kind_of(Factbase::CachedTerm, t)
85
+ refute_kind_of(Factbase::CachedTerm, Factbase::Term.new(:something, []))
84
86
  end
85
87
  end
metadata CHANGED
@@ -1,42 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-19 00:00:00.000000000 Z
10
+ date: 2025-03-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: backtrace
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.4.0
18
+ version: '0.4'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - ">="
23
+ - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.4.0
25
+ version: '0.4'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: decoor
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - ">="
30
+ - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.0.1
32
+ version: '0.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ">="
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.0.1
39
+ version: '0.0'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: json
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -51,20 +51,34 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '2.7'
54
+ - !ruby/object:Gem::Dependency
55
+ name: logger
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: loog
56
70
  requirement: !ruby/object:Gem::Requirement
57
71
  requirements:
58
- - - ">="
72
+ - - "~>"
59
73
  - !ruby/object:Gem::Version
60
- version: 0.6.0
74
+ version: '0.6'
61
75
  type: :runtime
62
76
  prerelease: false
63
77
  version_requirements: !ruby/object:Gem::Requirement
64
78
  requirements:
65
- - - ">="
79
+ - - "~>"
66
80
  - !ruby/object:Gem::Version
67
- version: 0.6.0
81
+ version: '0.6'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: nokogiri
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -83,30 +97,30 @@ dependencies:
83
97
  name: others
84
98
  requirement: !ruby/object:Gem::Requirement
85
99
  requirements:
86
- - - ">="
100
+ - - "~>"
87
101
  - !ruby/object:Gem::Version
88
- version: 0.0.3
102
+ version: '0.0'
89
103
  type: :runtime
90
104
  prerelease: false
91
105
  version_requirements: !ruby/object:Gem::Requirement
92
106
  requirements:
93
- - - ">="
107
+ - - "~>"
94
108
  - !ruby/object:Gem::Version
95
- version: 0.0.3
109
+ version: '0.0'
96
110
  - !ruby/object:Gem::Dependency
97
111
  name: tago
98
112
  requirement: !ruby/object:Gem::Requirement
99
113
  requirements:
100
- - - ">="
114
+ - - "~>"
101
115
  - !ruby/object:Gem::Version
102
- version: 0.0.2
116
+ version: '0.0'
103
117
  type: :runtime
104
118
  prerelease: false
105
119
  version_requirements: !ruby/object:Gem::Requirement
106
120
  requirements:
107
- - - ">="
121
+ - - "~>"
108
122
  - !ruby/object:Gem::Version
109
- version: 0.0.2
123
+ version: '0.0'
110
124
  - !ruby/object:Gem::Dependency
111
125
  name: yaml
112
126
  requirement: !ruby/object:Gem::Requirement