factbase 0.19.10 → 0.19.11
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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +94 -18
- data/lib/factbase/syntax.rb +1 -1
- data/lib/factbase/tallied.rb +1 -1
- data/lib/factbase/term.rb +6 -0
- data/lib/factbase/terms/base.rb +1 -3
- data/lib/factbase/terms/compare.rb +16 -1
- data/lib/factbase/terms/contains.rb +28 -0
- data/lib/factbase/terms/ends_with.rb +27 -0
- data/lib/factbase/terms/matches.rb +11 -1
- data/lib/factbase/terms/sprintf.rb +8 -0
- data/lib/factbase/terms/starts_with.rb +27 -0
- data/lib/factbase/terms/sum.rb +1 -1
- data/lib/factbase/terms/to_time.rb +9 -1
- data/lib/factbase/terms/when.rb +2 -3
- data/lib/factbase/to_json.rb +1 -1
- data/lib/factbase/to_xml.rb +1 -1
- data/lib/factbase/version.rb +1 -1
- data/lib/factbase.rb +0 -3
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 712cd682bc9457b563afc7a855e56cad191d2a302406e0e4e9f886ce7fa83af3
|
|
4
|
+
data.tar.gz: cd3a312b78a152b7c9631777473e7465648e6b57d31d713d37d93c63834048ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 013ff487bbd8d2379224361b7794d5219d3235c9c66f645ebe1d6dd928d2298f1a411240328839ea48e08f7dd4c1161cde173530c52f5f8a5afdab52c66cce98
|
|
7
|
+
data.tar.gz: cf57ff6a8f881224a2124306871d245cab380331905fd0ec1acecd381a46d636a7877867a8f1ef27611f1dafe4f1e777c1a01aa57f08b35eb3486682aef22ad3
|
data/Gemfile.lock
CHANGED
|
@@ -38,7 +38,7 @@ GEM
|
|
|
38
38
|
loog (0.8.0)
|
|
39
39
|
ellipsized
|
|
40
40
|
logger (~> 1.0)
|
|
41
|
-
minitest (6.0.
|
|
41
|
+
minitest (6.0.6)
|
|
42
42
|
drb (~> 2.0)
|
|
43
43
|
prism (~> 1.5)
|
|
44
44
|
minitest-reporters (1.8.0)
|
|
@@ -64,7 +64,7 @@ GEM
|
|
|
64
64
|
psych (5.3.1)
|
|
65
65
|
date
|
|
66
66
|
stringio
|
|
67
|
-
qbash (0.8.
|
|
67
|
+
qbash (0.8.4)
|
|
68
68
|
backtrace (> 0)
|
|
69
69
|
elapsed (> 0)
|
|
70
70
|
loog (> 0)
|
data/README.md
CHANGED
|
@@ -129,8 +129,7 @@ There are some boolean terms available in a query
|
|
|
129
129
|
* `(not b)` is the inverse of `b`
|
|
130
130
|
* `(or b1 b2 ...)` is `true` if at least one argument is `true`
|
|
131
131
|
* `(and b1 b2 ...)` — if all arguments are `true`
|
|
132
|
-
* `(when b1 b2)` — if `b1` is `
|
|
133
|
-
or `b1` is `false`
|
|
132
|
+
* `(when b1 b2)` — true if `b1` is `false`, or if both `b1` and `b2` are `true`
|
|
134
133
|
* `(exists p)` — if `p` property exists
|
|
135
134
|
* `(absent p)` — if `p` property is absent
|
|
136
135
|
* `(zero v)` — if any `v` equals to zero
|
|
@@ -145,6 +144,9 @@ There are string manipulators:
|
|
|
145
144
|
* `(concat v1 v2 v3 ...)` — concatenates all `v`
|
|
146
145
|
* `(sprintf v v1 v2 ...)` — creates a string by `v` format with params
|
|
147
146
|
* `(matches v s)` — if any `v` matches the `s` regular expression
|
|
147
|
+
* `(contains v s)` — if any value of `v` contains any value of `s` (substring match)
|
|
148
|
+
* `(starts_with v s)` — if any `v` starts with `s`
|
|
149
|
+
* `(ends_with v s)` — if any `v` ends with `s`
|
|
148
150
|
|
|
149
151
|
There are a few terms that return non-boolean values:
|
|
150
152
|
|
|
@@ -152,7 +154,7 @@ There are a few terms that return non-boolean values:
|
|
|
152
154
|
* `(size v)` is the cardinality of `v` (zero if `v` is `nil`)
|
|
153
155
|
* `(type v)` is the type of `v`
|
|
154
156
|
(`"String"`, `"Integer"`, `"Float"`, `"Time"`, or `"Array"`)
|
|
155
|
-
* `(either v1
|
|
157
|
+
* `(either v1 v2)` is `v2` if `v1` is `nil`
|
|
156
158
|
|
|
157
159
|
It's possible to modify the facts retrieved, on fly:
|
|
158
160
|
|
|
@@ -227,6 +229,80 @@ There are some system-level terms:
|
|
|
227
229
|
* `(env v1 v2)` returns the value of environment variable `v1` or the string
|
|
228
230
|
`v2` if it's not set
|
|
229
231
|
|
|
232
|
+
## Architecture
|
|
233
|
+
|
|
234
|
+
The entire database is a single flat [Ruby](https://www.ruby-lang.org/en/)
|
|
235
|
+
`Array` of `Hash` objects held in RAM (`Factbase#@maps`). There are no
|
|
236
|
+
tables, schemas, or type enforcement beyond four scalar types: `Integer`,
|
|
237
|
+
`Float`, `String`, and `Time`. This contrasts with
|
|
238
|
+
[SQLite](https://sqlite.org/) (fixed-column tables on disk) and
|
|
239
|
+
[MongoDB](https://www.mongodb.com/) (typed document collections). New
|
|
240
|
+
programmers must understand that all data vanishes on process exit unless
|
|
241
|
+
`export`/`import` is called explicitly.
|
|
242
|
+
|
|
243
|
+
Each property of a fact is a non-empty ordered set of values rather than a
|
|
244
|
+
single value. Assigning `f.foo = 1` then `f.foo = 2` produces
|
|
245
|
+
`f['foo'] == [1, 2]`; each assignment appends. Reading `f.foo` returns
|
|
246
|
+
the first element; `f['foo']` returns the full array. This accumulative
|
|
247
|
+
semantics differs from [SQL](https://www.iso.org/standard/76583.html)
|
|
248
|
+
(one value per column) and most NoSQL stores where assignment overwrites.
|
|
249
|
+
New programmers must expect multi-element arrays on every property read.
|
|
250
|
+
|
|
251
|
+
Queries use a custom Lisp-style
|
|
252
|
+
[S-expression](https://en.wikipedia.org/wiki/S-expression) language:
|
|
253
|
+
`(and (eq kind 'book') (gt age 10))`. `Factbase::Syntax` tokenizes and
|
|
254
|
+
parses a query string into an AST of `Factbase::Term` objects;
|
|
255
|
+
`Factbase::Query#each` evaluates that AST against every fact. This
|
|
256
|
+
differs from [SQL](https://www.iso.org/standard/76583.html),
|
|
257
|
+
[XPath](https://www.w3.org/TR/xpath-31/), and
|
|
258
|
+
[JSONPath](https://datatracker.ietf.org/doc/html/rfc9535). New
|
|
259
|
+
programmers add operators by implementing a term class, not by modifying
|
|
260
|
+
parser grammar.
|
|
261
|
+
|
|
262
|
+
Each query operator (`eq`, `gt`, `agg`, `join`, etc.) is a separate class
|
|
263
|
+
under `lib/factbase/terms/`. `Factbase::Term` holds a dispatch hash
|
|
264
|
+
(`@terms`) mapping operator symbols to instances and delegates `evaluate`
|
|
265
|
+
and `predict` calls there. This is not a class hierarchy — adding a new
|
|
266
|
+
operator requires a new file in `terms/` and a registration line in the
|
|
267
|
+
`Factbase::Term` constructor. New programmers extending the query
|
|
268
|
+
language must follow this two-step pattern.
|
|
269
|
+
|
|
270
|
+
Transactions are ACID and implemented via lazy copy-on-write journaling.
|
|
271
|
+
`Factbase#txn` wraps the array in `Factbase::LazyTaped`, which defers
|
|
272
|
+
physical duplication of hash objects until the first write. Inserts,
|
|
273
|
+
deletes, and property additions are tracked by Ruby `object_id`. On
|
|
274
|
+
commit the journal is replayed into the main array; raising
|
|
275
|
+
`Factbase::Rollback` discards it. Nesting transactions is explicitly
|
|
276
|
+
forbidden by `Factbase::Light`. This differs from SQLite's
|
|
277
|
+
[WAL](https://sqlite.org/wal.html) and PostgreSQL's
|
|
278
|
+
[MVCC](https://www.postgresql.org/docs/current/mvcc.html).
|
|
279
|
+
|
|
280
|
+
Cross-cutting capabilities — thread safety, indexing, constraint
|
|
281
|
+
validation, logging, and change counting — are added via decorators:
|
|
282
|
+
`Factbase::SyncFactbase`, `Factbase::IndexedFactbase`,
|
|
283
|
+
`Factbase::Rules`, `Factbase::Logged`, and `Factbase::Tallied`. The
|
|
284
|
+
[`decoor`](https://github.com/yegor256/decoor) gem provides delegation
|
|
285
|
+
boilerplate. The bare `Factbase` class is not thread-safe; new
|
|
286
|
+
programmers must wrap it with `SyncFactbase` before sharing across
|
|
287
|
+
threads.
|
|
288
|
+
|
|
289
|
+
Persistence uses Ruby's
|
|
290
|
+
[`Marshal`](https://ruby-doc.org/core/Marshal.html), serializing the
|
|
291
|
+
internal array of hashes to a binary blob via `Marshal.dump`. The format
|
|
292
|
+
is Ruby-version-specific and not portable across major Ruby versions or
|
|
293
|
+
platforms, unlike [JSON](https://www.json.org/json-en.html) or
|
|
294
|
+
[Protocol Buffers](https://protobuf.dev/). Output-only decorators
|
|
295
|
+
`Factbase::ToJSON`, `Factbase::ToXML`, and `Factbase::ToYAML` exist but
|
|
296
|
+
do not support round-trip import.
|
|
297
|
+
|
|
298
|
+
`Factbase::IndexedFactbase` lazily builds a hash-based inverted index for
|
|
299
|
+
equality queries, keyed by array `object_id`, property name, and
|
|
300
|
+
operator. The index is built incrementally on each query and invalidated
|
|
301
|
+
entirely on any mutation (delete or property addition). Without this
|
|
302
|
+
decorator every `query#each` call performs a full linear scan over all
|
|
303
|
+
facts. New programmers should add `IndexedFactbase` whenever the
|
|
304
|
+
factbase holds more than a few thousand facts.
|
|
305
|
+
|
|
230
306
|
## How to contribute
|
|
231
307
|
|
|
232
308
|
Read
|
|
@@ -250,24 +326,24 @@ This is the result of the benchmark:
|
|
|
250
326
|
<!-- benchmark_begin -->
|
|
251
327
|
```text
|
|
252
328
|
user
|
|
253
|
-
void scan 0.
|
|
254
|
-
20k facts: export:
|
|
255
|
-
20k facts: import:
|
|
256
|
-
50k facts: read 0.
|
|
257
|
-
50k facts: read in txn 0.
|
|
258
|
-
50k facts: insert 0.
|
|
259
|
-
50k facts: insert in txn 0.
|
|
260
|
-
50k facts: modify 1.
|
|
261
|
-
50k facts: modify in txn 2.
|
|
262
|
-
12k facts: large query: match 3k
|
|
263
|
-
12k facts: large query: match 3k in txn
|
|
264
|
-
12k facts: large query: match zero
|
|
265
|
-
12k facts: large query: match zero in txn
|
|
329
|
+
void scan 0.001049
|
|
330
|
+
20k facts: export: 2980KB 0.840193
|
|
331
|
+
20k facts: import: 2980KB 1.033492
|
|
332
|
+
50k facts: read 0.000179
|
|
333
|
+
50k facts: read in txn 0.002282
|
|
334
|
+
50k facts: insert 0.000082
|
|
335
|
+
50k facts: insert in txn 0.000613
|
|
336
|
+
50k facts: modify 1.427349
|
|
337
|
+
50k facts: modify in txn 2.773065
|
|
338
|
+
12k facts: large query: match 3k 13.166170
|
|
339
|
+
12k facts: large query: match 3k in txn 18.502015
|
|
340
|
+
12k facts: large query: match zero 14.234812
|
|
341
|
+
12k facts: large query: match zero in txn 19.847547
|
|
266
342
|
```
|
|
267
343
|
|
|
268
344
|
The results were calculated in [this GHA job][benchmark-gha]
|
|
269
|
-
on 2026-
|
|
345
|
+
on 2026-05-23 at 03:40,
|
|
270
346
|
on Linux with 4 CPUs.
|
|
271
347
|
<!-- benchmark_end -->
|
|
272
348
|
|
|
273
|
-
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/
|
|
349
|
+
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/26322478578
|
data/lib/factbase/syntax.rb
CHANGED
|
@@ -142,7 +142,7 @@ class Factbase::Syntax
|
|
|
142
142
|
t[1..-2]
|
|
143
143
|
elsif t.match?(/^(\+|-)?[0-9]+$/)
|
|
144
144
|
t.to_i
|
|
145
|
-
elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e
|
|
145
|
+
elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e(\+|-)[0-9]+)?$/)
|
|
146
146
|
t.to_f
|
|
147
147
|
elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
|
|
148
148
|
Time.parse(t)
|
data/lib/factbase/tallied.rb
CHANGED
|
@@ -8,7 +8,7 @@ require 'others'
|
|
|
8
8
|
require_relative '../factbase'
|
|
9
9
|
require_relative 'churn'
|
|
10
10
|
|
|
11
|
-
# A decorator of a Factbase, that
|
|
11
|
+
# A decorator of a Factbase, that counts all operations and then returns
|
|
12
12
|
# an instance of Factbase::Churn.
|
|
13
13
|
#
|
|
14
14
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
data/lib/factbase/term.rb
CHANGED
|
@@ -12,6 +12,9 @@ require_relative 'terms/prev'
|
|
|
12
12
|
require_relative 'terms/concat'
|
|
13
13
|
require_relative 'terms/sprintf'
|
|
14
14
|
require_relative 'terms/matches'
|
|
15
|
+
require_relative 'terms/contains'
|
|
16
|
+
require_relative 'terms/starts_with'
|
|
17
|
+
require_relative 'terms/ends_with'
|
|
15
18
|
require_relative 'terms/traced'
|
|
16
19
|
require_relative 'terms/assert'
|
|
17
20
|
require_relative 'terms/env'
|
|
@@ -108,6 +111,9 @@ class Factbase::Term < Factbase::TermBase
|
|
|
108
111
|
concat: Factbase::Concat.new(operands),
|
|
109
112
|
sprintf: Factbase::Sprintf.new(operands),
|
|
110
113
|
matches: Factbase::Matches.new(operands),
|
|
114
|
+
contains: Factbase::Contains.new(operands),
|
|
115
|
+
starts_with: Factbase::StartsWith.new(operands),
|
|
116
|
+
ends_with: Factbase::EndsWith.new(operands),
|
|
111
117
|
traced: Factbase::Traced.new(operands),
|
|
112
118
|
assert: Factbase::Assert.new(operands),
|
|
113
119
|
env: Factbase::Env.new(operands),
|
data/lib/factbase/terms/base.rb
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Base class for all terms.
|
|
7
7
|
# Author:: Volodya Lombrozo (volodya.lombrozo@gmail.com)
|
|
8
8
|
# Copyright:: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
9
9
|
# License:: MIT
|
|
10
|
-
|
|
11
|
-
# Base class for all terms.
|
|
12
10
|
class Factbase::TermBase
|
|
13
11
|
# Turns it into a string.
|
|
14
12
|
# @return [String] The string of it
|
|
@@ -31,8 +31,23 @@ class Factbase::Compare < Factbase::TermBase
|
|
|
31
31
|
l = l.floor if l.is_a?(Time) && @op == :==
|
|
32
32
|
rights.any? do |r|
|
|
33
33
|
r = r.floor if r.is_a?(Time) && @op == :==
|
|
34
|
-
l
|
|
34
|
+
_compare(l, r)
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Compare values with a contextual error if Ruby rejects the operands.
|
|
42
|
+
# @param [Object] left Left value
|
|
43
|
+
# @param [Object] right Right value
|
|
44
|
+
# @return [Boolean] The result of the comparison
|
|
45
|
+
def _compare(left, right)
|
|
46
|
+
left.send(@op, right)
|
|
47
|
+
rescue ArgumentError => e
|
|
48
|
+
raise(
|
|
49
|
+
"Cannot compare #{left.inspect} (#{left.class}) " \
|
|
50
|
+
"with #{right.inspect} (#{right.class}) using (compare #{@op}): #{e.message}"
|
|
51
|
+
)
|
|
52
|
+
end
|
|
38
53
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative 'base'
|
|
7
|
+
require_relative 'compare'
|
|
8
|
+
|
|
9
|
+
# Represents a 'contains' term in the Factbase.
|
|
10
|
+
# Returns true if any value of the left operand contains any value of the right
|
|
11
|
+
# as a substring. Operates on string values via `String#include?`.
|
|
12
|
+
class Factbase::Contains < Factbase::TermBase
|
|
13
|
+
# Constructor.
|
|
14
|
+
# @param [Array] operands Operands
|
|
15
|
+
def initialize(operands)
|
|
16
|
+
super()
|
|
17
|
+
@op = Factbase::Compare.new(:include?, operands)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Evaluate term on a fact.
|
|
21
|
+
# @param [Factbase::Fact] fact The fact
|
|
22
|
+
# @param [Array<Factbase::Fact>] maps All maps available
|
|
23
|
+
# @param [Factbase] fb Factbase to use for sub-queries
|
|
24
|
+
# @return [Boolean] True if any left value includes any right value
|
|
25
|
+
def evaluate(fact, maps, fb)
|
|
26
|
+
@op.evaluate(fact, maps, fb)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative 'base'
|
|
7
|
+
require_relative 'compare'
|
|
8
|
+
|
|
9
|
+
# Represents an 'ends_with' term in the Factbase.
|
|
10
|
+
# Returns true if any value of the left operand ends with any value of the right.
|
|
11
|
+
class Factbase::EndsWith < Factbase::TermBase
|
|
12
|
+
# Constructor.
|
|
13
|
+
# @param [Array] operands Operands
|
|
14
|
+
def initialize(operands)
|
|
15
|
+
super()
|
|
16
|
+
@op = Factbase::Compare.new(:end_with?, operands)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Evaluate term on a fact.
|
|
20
|
+
# @param [Factbase::Fact] fact The fact
|
|
21
|
+
# @param [Array<Factbase::Fact>] maps All maps available
|
|
22
|
+
# @param [Factbase] fb Factbase to use for sub-queries
|
|
23
|
+
# @return [Boolean] True if any left value ends with any right value
|
|
24
|
+
def evaluate(fact, maps, fb)
|
|
25
|
+
@op.evaluate(fact, maps, fb)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -14,6 +14,7 @@ class Factbase::Matches < Factbase::TermBase
|
|
|
14
14
|
def initialize(operands)
|
|
15
15
|
super()
|
|
16
16
|
@operands = operands
|
|
17
|
+
@regexps = {}
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
# Evaluate term on a fact.
|
|
@@ -29,6 +30,15 @@ class Factbase::Matches < Factbase::TermBase
|
|
|
29
30
|
re = _values(1, fact, maps, fb)
|
|
30
31
|
raise 'Regexp is nil' if re.nil?
|
|
31
32
|
raise 'Exactly one regexp is expected' unless re.size == 1
|
|
32
|
-
str[0].to_s.match?(re[0])
|
|
33
|
+
str[0].to_s.match?(regexp(re[0]))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def regexp(pattern)
|
|
39
|
+
key = pattern.to_s
|
|
40
|
+
@regexps[key] ||= Regexp.new(key)
|
|
41
|
+
rescue RegexpError => e
|
|
42
|
+
raise "Invalid regexp '#{key}': #{e.message}"
|
|
33
43
|
end
|
|
34
44
|
end
|
|
@@ -24,6 +24,14 @@ class Factbase::Sprintf < Factbase::TermBase
|
|
|
24
24
|
def evaluate(fact, maps, fb)
|
|
25
25
|
fmt = _values(0, fact, maps, fb)[0]
|
|
26
26
|
ops = (1..(@operands.length - 1)).map { |i| _values(i, fact, maps, fb)&.first }
|
|
27
|
+
formatted(fmt, ops)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def formatted(fmt, ops)
|
|
27
33
|
format(*([fmt] + ops))
|
|
34
|
+
rescue ArgumentError => e
|
|
35
|
+
raise "Cannot format #{ops.inspect} with '#{fmt}' in (sprintf ...): #{e.message}"
|
|
28
36
|
end
|
|
29
37
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative 'base'
|
|
7
|
+
require_relative 'compare'
|
|
8
|
+
|
|
9
|
+
# Represents a 'starts_with' term in the Factbase.
|
|
10
|
+
# Returns true if any value of the left operand starts with any value of the right.
|
|
11
|
+
class Factbase::StartsWith < Factbase::TermBase
|
|
12
|
+
# Constructor.
|
|
13
|
+
# @param [Array] operands Operands
|
|
14
|
+
def initialize(operands)
|
|
15
|
+
super()
|
|
16
|
+
@op = Factbase::Compare.new(:start_with?, operands)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Evaluate term on a fact.
|
|
20
|
+
# @param [Factbase::Fact] fact The fact
|
|
21
|
+
# @param [Array<Factbase::Fact>] maps All maps available
|
|
22
|
+
# @param [Factbase] fb Factbase to use for sub-queries
|
|
23
|
+
# @return [Boolean] True if any left value starts with any right value
|
|
24
|
+
def evaluate(fact, maps, fb)
|
|
25
|
+
@op.evaluate(fact, maps, fb)
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/factbase/terms/sum.rb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
require_relative 'base'
|
|
7
7
|
|
|
8
8
|
# This class represents a specialized 'sum' term.
|
|
9
|
-
# This term
|
|
9
|
+
# This term calculates the sum of values for a specified key.
|
|
10
10
|
class Factbase::Sum < Factbase::TermBase
|
|
11
11
|
# Constructor.
|
|
12
12
|
# @param [Array] operands Operands
|
|
@@ -23,6 +23,14 @@ class Factbase::ToTime < Factbase::TermBase
|
|
|
23
23
|
assert_args(1)
|
|
24
24
|
vv = _values(0, fact, maps, fb)
|
|
25
25
|
return nil if vv.nil?
|
|
26
|
-
|
|
26
|
+
parse(vv[0])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def parse(value)
|
|
32
|
+
Time.parse(value.to_s)
|
|
33
|
+
rescue ArgumentError => e
|
|
34
|
+
raise "Cannot parse '#{value}' as Time in (to_time ...): #{e.message}"
|
|
27
35
|
end
|
|
28
36
|
end
|
data/lib/factbase/terms/when.rb
CHANGED
|
@@ -22,8 +22,7 @@ class Factbase::When < Factbase::TermBase
|
|
|
22
22
|
# @return [Boolean] True if first operand is false OR both are true
|
|
23
23
|
def evaluate(fact, maps, fb)
|
|
24
24
|
assert_args(2)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
!a.evaluate(fact, maps, fb) || (a.evaluate(fact, maps, fb) && b.evaluate(fact, maps, fb))
|
|
25
|
+
return true unless @operands[0].evaluate(fact, maps, fb)
|
|
26
|
+
@operands[1].evaluate(fact, maps, fb)
|
|
28
27
|
end
|
|
29
28
|
end
|
data/lib/factbase/to_json.rb
CHANGED
|
@@ -9,7 +9,7 @@ require_relative '../factbase/flatten'
|
|
|
9
9
|
|
|
10
10
|
# Factbase to JSON converter.
|
|
11
11
|
#
|
|
12
|
-
# This class helps converting an entire Factbase to
|
|
12
|
+
# This class helps converting an entire Factbase to JSON format, for example:
|
|
13
13
|
#
|
|
14
14
|
# require 'factbase/to_json'
|
|
15
15
|
# fb = Factbase.new
|
data/lib/factbase/to_xml.rb
CHANGED
|
@@ -10,7 +10,7 @@ require_relative '../factbase/flatten'
|
|
|
10
10
|
|
|
11
11
|
# Factbase to XML converter.
|
|
12
12
|
#
|
|
13
|
-
# This class helps converting an entire Factbase to
|
|
13
|
+
# This class helps converting an entire Factbase to XML format, for example:
|
|
14
14
|
#
|
|
15
15
|
# require 'factbase/to_xml'
|
|
16
16
|
# fb = Factbase.new
|
data/lib/factbase/version.rb
CHANGED
data/lib/factbase.rb
CHANGED
|
@@ -102,9 +102,6 @@ class Factbase
|
|
|
102
102
|
#
|
|
103
103
|
# A fact, when inserted, is empty. It doesn't contain any properties.
|
|
104
104
|
#
|
|
105
|
-
# The operation is thread-safe, meaning that different threads may
|
|
106
|
-
# insert facts in parallel without breaking the consistency of the factbase.
|
|
107
|
-
#
|
|
108
105
|
# @return [Factbase::Fact] The fact just inserted
|
|
109
106
|
def insert
|
|
110
107
|
map = {}
|
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.19.
|
|
4
|
+
version: 0.19.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -222,11 +222,13 @@ files:
|
|
|
222
222
|
- lib/factbase/terms/boolean.rb
|
|
223
223
|
- lib/factbase/terms/compare.rb
|
|
224
224
|
- lib/factbase/terms/concat.rb
|
|
225
|
+
- lib/factbase/terms/contains.rb
|
|
225
226
|
- lib/factbase/terms/count.rb
|
|
226
227
|
- lib/factbase/terms/defn.rb
|
|
227
228
|
- lib/factbase/terms/div.rb
|
|
228
229
|
- lib/factbase/terms/either.rb
|
|
229
230
|
- lib/factbase/terms/empty.rb
|
|
231
|
+
- lib/factbase/terms/ends_with.rb
|
|
230
232
|
- lib/factbase/terms/env.rb
|
|
231
233
|
- lib/factbase/terms/eq.rb
|
|
232
234
|
- lib/factbase/terms/exists.rb
|
|
@@ -255,6 +257,7 @@ files:
|
|
|
255
257
|
- lib/factbase/terms/size.rb
|
|
256
258
|
- lib/factbase/terms/sorted.rb
|
|
257
259
|
- lib/factbase/terms/sprintf.rb
|
|
260
|
+
- lib/factbase/terms/starts_with.rb
|
|
258
261
|
- lib/factbase/terms/sum.rb
|
|
259
262
|
- lib/factbase/terms/times.rb
|
|
260
263
|
- lib/factbase/terms/to_float.rb
|