Blog of Raivo Laanemets

Software development and personal stories.

Docstore database engine for SWI-Prolog

On 2014-01-02

Today I released the version 1.0.0 of my database engine for SWI-Prolog. It is an in-process in-memory transactional engine that journals changes into a log file. It started as a project to get more flexibility out of persistency.pl. The existing library was not thread-safe which required wrapping every call with a mutex. I also had structured my other code around property lists (or option lists, which are just terms like [key(value)]). That required conversion from one representation to another.

Here is some example usage:

?- ds_insert(vehicle{year: 1926, make: chrysler, model: imperial}).
?- ds_insert(vehicle{year: 1953, make: chevrolet, model: corvette}).
?- ds_insert(vehicle{year: 1954, make: cadillac, model: fleetwood}).

?- ds_all(vehicle, List).
List = [
    vehicle{'$id':'f3012622-cacb-4d7f-a22a-c36305274a80',
        make:chrysler, model:imperial, year:1926},
    vehicle{'$id':'23418d47-5835-41ff-a6b8-8748f3b2163e',
        make:chevrolet, model:corvette, year:1953},
    vehicle{'$id':'8c79f80f-d43e-4fad-a1bb-5fca23a195e0',
        make:cadillac, model:fleetwood, year:1954}].

This is not the first database system I have built. I have done that in Turbo Pascal, too, in high school. It had excellent support for record files (typed files). But I think it had no transactions or anything else to support consistency. When the power goes out in the middle of write, some data might get not written. I found that transactions are easy to implement in Prolog. I currently use in-memory log during the transaction and write it into the real log file at the end of the transaction. Each transaction must end with a marker term in the log file. When the end term is missing the transaction is considered failed and the logged actions from it are discarded. This makes consistency easy to get. In my engine I only allow to run single transaction concurrently. This makes it less powerful than real databases but it is simpler to implement.

SWI-Prolog 7.x got a new data structure called dicts (they are also used in the example above). At the first I disliked them but now I see that they are good alternative for property lists. They are supposed to be more efficient, too. I had not seen them when I started the database project but decided to convert all of its predicates to accept/retrieve dicts. The internal storage uses eav(id, key, value) representation anyway, not dicts nor property lists. This makes journaling individual key value updates cheaper. The journal, by the way, is just a text file consisting of assert/retract calls and transaction begin/end markers.

The database supports hooks before_save and before_remove. Hooks will participate in the currently running transaction. Failing hook or one that throws an exception will discard the transaction and changes made by it.

The old version of the database is powering this blog. I have not yet migrated all other libraries onto SWI 7.x and dicts but I hope to do it soon.

It's also my first Prolog pack I have published. I accidentally published version 0.0.1 when I tried out installation on my machine. SWI asks to "verify package status" when you install a pack. I had no idea it actually publishes it when you answer Y. Well, at least it's released now.

  • The pack is available at here.
  • GitHub repository is here. Pull requests and issue reports are welcome.
  • API documentation for the latest version is here.