Class: Factbase

Inherits:
Object
  • Object
show all
Defined in:
lib/factbase.rb

Overview

A factbase, which essentially is a NoSQL one-table in-memory database with a Lisp-ish query interface.

This class is an entry point to a factbase. For example, this is how you add a new “fact” to a factbase, then put two properties into it, and then find this fact with a simple search.

fb = Factbase.new
f = fb.insert # new fact created
f.name = 'Jeff Lebowski'
f.age = 42
found = f.query('(gt 20 age)').each.to_a[0]
assert(found.age == 42)

Every fact is a key-value hash map. Every value is a non-empty set of values. Consider this example of creating a factbase with a single fact inside:

fb = Factbase.new
f = fb.insert
f.name = 'Jeff'
f.name = 'Walter'
f.age = 42
f.age = 'unknown'
f.place = 'LA'
puts f.to_json

This will print the following JSON:

{
  'name': ['Jeff', 'Walter'],
  'age': [42, 'unknown'],
  'place: 'LA'
}

Value sets, as you can see, allow data of different types. However, there are only four types are allowed: Integer, Float, String, and Time.

A factbase may be exported to a file and then imported back:

fb1 = Factbase.new
File.binwrite(file, fb1.export)
fb2 = Factbase.new # it's empty
fb2.import(File.binread(file))

It’s impossible to delete properties of a fact. It is however possible to delete the entire fact, with the help of the query() and then delete!() methods.

It’s important to use binwrite and binread, because the content is a chain of bytes, not a text.

Author

Yegor Bugayenko ([email protected])

Copyright

Copyright © 2024 Yegor Bugayenko

License

MIT

Defined Under Namespace

Classes: Fact, Inv, Looged, Pre, Query, Rollback, Rules, Syntax, Term, ToJSON, ToXML, ToYAML, Tuples

Constant Summary collapse

VERSION =

Current version of the gem (changed by .rultor.yml on every release)

'0.0.0'

Instance Method Summary collapse

Constructor Details

#initialize(facts = []) ⇒ Factbase

Constructor.

Parameters:

  • facts (Array<Hash>) (defaults to: [])

    Array of facts to start with



89
90
91
92
# File 'lib/factbase.rb', line 89

def initialize(facts = [])
  @maps = facts
  @mutex = Mutex.new
end

Instance Method Details

#dupFactbase

Make a duplicate of this factbase.

Returns:



96
97
98
# File 'lib/factbase.rb', line 96

def dup
  Factbase.new(@maps.dup)
end

#exportBytes

Export it into a chain of bytes.

Here is how you can export it to a file, for example:

fb = Factbase.new
fb.insert.foo = 42
File.binwrite("foo.fb", fb.export)

The data is binary, it’s not a text!

Returns:

  • (Bytes)

    The chain of bytes



191
192
193
# File 'lib/factbase.rb', line 191

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 added to them.

Parameters:

  • bytes (Bytes)

    Byte array to import



206
207
208
# File 'lib/factbase.rb', line 206

def import(bytes)
  @maps += Marshal.load(bytes)
end

#insertFactbase::Fact

Insert a new fact and return it.

A fact, when inserted, is empty. It doesn’t contain any properties.

The operation is thread-safe, meaning that you different threads may insert facts parallel without breaking the consistency of the factbase.

Returns:



114
115
116
117
118
119
120
121
# File 'lib/factbase.rb', line 114

def insert
  require_relative 'factbase/fact'
  map = {}
  @mutex.synchronize do
    @maps << map
  end
  Factbase::Fact.new(@mutex, map)
end

#query(query) ⇒ 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.

Parameters:

  • query (String)

    The query to use for selections



141
142
143
144
# File 'lib/factbase.rb', line 141

def query(query)
  require_relative 'factbase/query'
  Factbase::Query.new(@maps, @mutex, query)
end

#sizeInteger

Size.

Returns:

  • (Integer)

    How many facts are in there



102
103
104
# File 'lib/factbase.rb', line 102

def size
  @maps.size
end

#txn(this = self) ⇒ Object

Run an ACID transaction, which will either modify the factbase or rollback in case of an error.

If necessary to terminate a transaction and roolback all changes, you should raise the Factbase::Rollback exception:

fb = Factbase.new
fb.txn do |fbt|
  fbt.insert.bar = 42
  raise Factbase::Rollback
end

A the end of this script, the factbase will be empty. No facts will inserted and all changes that happened in the block will be rolled back.

Parameters:

  • this (Factbase) (defaults to: self)

    The factbase to use (don’t provide this param)



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/factbase.rb', line 162

def txn(this = self)
  copy = this.dup
  begin
    yield copy
  rescue Factbase::Rollback
    return
  end
  @mutex.synchronize do
    after = Marshal.load(copy.export)
    after.each_with_index do |m, i|
      @maps << {} if i >= @maps.size
      m.each do |k, v|
        @maps[i][k] = v
      end
    end
  end
end