Class: Factbase
- Inherits:
-
Object
- Object
- Factbase
- Defined in:
- lib/factbase.rb,
lib/factbase/version.rb
Overview
Just version.
- Author
-
Yegor Bugayenko (yegor256@gmail.com)
- Copyright
-
Copyright © 2024-2026 Yegor Bugayenko
- License
-
MIT
Defined Under Namespace
Modules: CachedTerm, IndexedTerm Classes: Absent, Accum, Agg, Always, And, Arithmetic, As, Assert, Best, Boolean, CachedFact, CachedFactbase, CachedQuery, Churn, Compare, Concat, Contains, Count, Defn, Div, Either, Empty, EndsWith, Env, Eq, Exists, Fact, FactAsYaml, First, Flatten, Fuzz, Gt, Gte, Head, Impatient, IndexedAbsent, IndexedAnd, IndexedEq, IndexedExists, IndexedFact, IndexedFactbase, IndexedGt, IndexedGte, IndexedLt, IndexedLte, IndexedNot, IndexedOne, IndexedOr, IndexedQuery, IndexedUnique, Inv, Inverted, Join, LazyTaped, Light, Logged, Lt, Lte, Many, Matches, Max, Min, Minus, Never, Nil, Not, Nth, One, Or, Plus, Pre, Prev, Query, Rollback, Rules, Simplified, Size, Sorted, Sprintf, StartsWith, Sum, SyncFactbase, SyncQuery, Syntax, Tallied, Taped, Tee, Term, TermBase, Times, ToFloat, ToInteger, ToJSON, ToString, ToTime, ToXML, ToYAML, Traced, Type, Undef, Unique, When, Zero
Constant Summary collapse
- VERSION =
'0.19.12'
Instance Method Summary collapse
-
#each {|fact| ... } ⇒ Integer, Enumerator
Iterate over all facts yielding plain hashes.
-
#export ⇒ String
Export it into a chain of bytes.
-
#import(bytes) ⇒ Object
Import from a chain of bytes.
-
#initialize(maps = []) ⇒ Factbase
constructor
Constructor.
-
#insert ⇒ Factbase::Fact
Insert a new fact and return it.
-
#query(term, maps = nil) ⇒ Object
Create a query capable of iterating.
-
#size ⇒ Integer
Size, the total number of facts in the factbase.
-
#to_term(query) ⇒ Factbase::Term
Convert a query to a term.
-
#txn ⇒ Factbase::Churn
Run an ACID transaction, which will either modify the factbase or rollback in case of an error.
Constructor Details
#initialize(maps = []) ⇒ Factbase
Constructor.
91 92 93 |
# File 'lib/factbase.rb', line 91 def initialize(maps = []) @maps = maps end |
Instance Method Details
#each {|fact| ... } ⇒ Integer, Enumerator
Iterate over all facts yielding plain hashes.
104 105 106 |
# File 'lib/factbase.rb', line 104 def each(&) @maps.each(&) end |
#export ⇒ String
226 227 228 |
# File 'lib/factbase.rb', line 226 def export Marshal.dump(@maps) end |
#import(bytes) ⇒ Object
Import from a chain of bytes.
Here is how you can read it from a file, for example:
fb = Factbase.new
fb.import(File.binread("foo.fb"))
The facts that existed in the factbase before importing will remain there. The facts from the incoming byte stream will be added to them.
This method supports both the original format (Array of maps) and the IndexedFactbase format (Hash with :maps and :idx keys).
SECURITY: the input must come from a source you trust, because it is deserialized with Marshal.load. Loading a Marshal stream crafted by an attacker can execute arbitrary code in the calling process, so never call import on bytes received over the network, read from a user-supplied path, or pulled from any other untrusted channel without an out-of-band integrity check. See the official Ruby security notes for Marshal.load at docs.ruby-lang.org/en/3.3/security_rdoc.html#label-Marshal.load for details.
254 255 256 257 258 259 260 261 262 263 |
# File 'lib/factbase.rb', line 254 def import(bytes) raise(StandardError, 'Empty input, cannot load a factbase') if bytes.empty? data = Marshal.load(bytes) @maps += if data.is_a?(Hash) && data.key?(:maps) Marshal.load(data[:maps]) else data end end |
#insert ⇒ Factbase::Fact
Insert a new fact and return it.
A fact, when inserted, is empty. It doesn’t contain any properties.
113 114 115 116 117 118 |
# File 'lib/factbase.rb', line 113 def insert map = {} @maps << map require_relative('factbase/fact') Factbase::Fact.new(map) end |
#query(term, maps = nil) ⇒ Object
Create a query capable of iterating.
There is a Lisp-like syntax, for example:
(eq title 'Object Thinking')
(gt time 2024-03-23T03:21:43Z)
(gt cost 42)
(exists seenBy)
(and
(eq foo 42.998)
(or
(gt bar 200)
(absent zzz)))
The full list of terms available in the query you can find in the README.md file of the repository.
139 140 141 142 143 144 |
# File 'lib/factbase.rb', line 139 def query(term, maps = nil) maps ||= @maps term = to_term(term) if term.is_a?(String) require_relative('factbase/query') Factbase::Query.new(maps, term, self) end |
#size ⇒ Integer
Size, the total number of facts in the factbase.
97 98 99 |
# File 'lib/factbase.rb', line 97 def size @maps.size end |
#to_term(query) ⇒ Factbase::Term
Convert a query to a term.
149 150 151 152 |
# File 'lib/factbase.rb', line 149 def to_term(query) require_relative('factbase/syntax') Factbase::Syntax.new(query).to_term end |
#txn ⇒ Factbase::Churn
Run an ACID transaction, which will either modify the factbase or rollback in case of an error.
If necessary to terminate a transaction and rollback all changes, you should raise the Factbase::Rollback exception:
fb = Factbase.new
fb.txn do |fbt|
fbt.insert. = 42
raise Factbase::Rollback
end
At the end of this script, the factbase will be empty. No facts will be inserted and all changes that happened in the block will be rolled back.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/factbase.rb', line 170 def txn require_relative('factbase/lazy_taped') taped = Factbase::LazyTaped.new(@maps) require_relative('factbase/churn') churn = Factbase::Churn.new catch(:commit) do require_relative('factbase/light') commit = false catch(:rollback) do yield(Factbase::Light.new(Factbase.new(taped))) commit = true end return churn unless commit rescue Factbase::Rollback return churn end seen = {}.compare_by_identity garbage = {}.compare_by_identity taped.deleted.each do |oid| original = @maps.find { |m| m.object_id == oid } next if original.nil? garbage[original] = true churn.append(0, 1, 0) end taped.inserted.each do |oid| b = taped.find_by_object_id(oid) next if b.nil? next if seen.key?(b) seen[b] = true @maps << b churn.append(1, 0, 0) end taped.added.each do |oid| b = taped.find_by_object_id(oid) next if b.nil? next if seen.key?(b) original = taped.source_of(b) garbage[original] = true if original @maps << b churn.append(0, 0, 1) end @maps.delete_if { |m| garbage.key?(m) } unless garbage.empty? churn end |