An object-hash mapping for Redis in Common Lisp

Upstream URL


Sebastian Christ <>



file: file: file: file:

This is an implementation of Ohm in Common Lisp with some inspiration from Crane and datafly.

Ohm is an object-hash mapping for Redis.


1.1Starting and connecting Redis

First of all, Redis must be up and running. You can start Redis with the command line

$ redis-server

Then load CL-OHM:

(ql:quickload :cl-ohm)

Without configuration Redis runs on localhost:6379. If you're using a different host or port you have to configure CL-OHM. For example, if Redis is running on on port 12455 than you must setup CL-OHM like this:

(ohm:setup-redis-connection :host #(198 162 55 12) :port 12455)

1.2Mapping objects to Redis

Use ohm:define-ohm-model to specify your models.

(ohm:define-ohm-model person ()
  :attributes ((first-name :indexp t)
               (last-name :indexp t)
               (email :uniquep t)))

You can create new persisted objects with CREATE:

(ohm:create 'person :first-name "John" :last-name "McCarthy")

Attributes are setfable like ordinary objects slots (Note: if you don't provide readers or writers for an attribute, an accessor will be created) but has to be explicitly saved to be persisted.

(ohm:create 'person :first-name "Bill")
(setf (first-name *) "William")
(ohm:save **)

1.3Loading objects from the data store

When you know an object's ID then you can load it with filter-id

(ohm:filter-id 'person "5")
;;; or
(ohm:filter-id 'person 5)


For each attribute marked with :INDEXP and index gets created. With this index it is possible to load objects by their values.

(ohm:filter 'person :first-name "Bill")

This load all objects with first-name=Bill. Indexed attributes can be combined in FILTER.

(ohm:filter 'person :first-name "Bill" :last-name "Miller")

If you omit any attribute specifiers from FILTER than all objects for the given type are retrieved.

(ohm:filter 'person)

1.4Unique values

Each attribute marked as :UNIQUEP must be unique for all instances of a given model. Considering the person model from above this means two instances cannot have the same email. :UNIQUEP also creates an index, query-able with FILTER-WITH.

(ohm:filter-with 'person :email "")

This load the person object with


Counters let you count atomically.

(ohm:define-ohm-model candidate (person)
  :counters (votes))

(let ((candidate (create 'candidate :first-name "Bill")))
  (ohm:incr (votes candidate))
  (ohm:incr (votes candidate))

  (ohm:counter (votes candidate))       ;=> 2

  (ohm:decr (votes candidate) 2)

  (ohm:counter (votes candidate)))      ;=> 0

1.6Sets and Lists

Each model can define sets or lists as attributes. Sets and lists can hold other persisted objects defined by DEFINE-OHM-MODEL. Therefore you most provide the set's or list's element-type.

(ohm:define-ohm-model tag ()
  :attributes ((name :indexp t)))

(ohm:define-ohm-model post ()
  :lists ((authors :element-type person))
  :sets ((tags :element-type tag)))

CL-OHM persisted objects are internally stored in sets.

(ohm:create 'person :first-name "Donald" :last-name "Duck")
(ohm:filter 'person) ;=> #<CL-OHM::OHM-SET {1009FAB643}>

This lets you combine the FITLER function with set operations.

1.6.1Set operations

Creating some test data:

(ohm:create 'person :first-name "Donald" :last-name "Duck")
(ohm:create 'person :first-name "Daisy" :last-name "Duck")
(ohm:create 'person :first-name "Gladstone" :last-name "Gander")

Creating the union of persons named Duck and persons named Gander:

(ohm:elements (ohm:union (ohm:filter 'person :last-name "Duck")
                         (ohm:filter 'person :last-name "Gander")))
(#<PERSON CL-OHM::ID="1" FIRST-NAME="Donald" LAST-NAME="Duck" {1008745153}>
 #<PERSON CL-OHM::ID="2" FIRST-NAME="Daisy" LAST-NAME="Duck" {1008745333}>
 #<PERSON CL-OHM::ID="3" FIRST-NAME="Gladstone" LAST-NAME="Gander"

Use EXCEPT to exclude objects with specific properties. Say, exclude persons named Gander from all persons:

(ohm:elements (ohm:except (ohm:filter 'person) ; all persons
                          (ohm:filter 'person :last-name "Gander")))
(#<PERSON CL-OHM::ID="1" FIRST-NAME="Donald" LAST-NAME="Duck" {1008AA3963}>
 #<PERSON CL-OHM::ID="2" FIRST-NAME="Daisy" LAST-NAME="Duck" {1008AA3B43}>)

Use COMBINE to limit the resulting set. Say, all persons with last name Duck and first name Donald:

(ohm:elements (ohm:combine (ohm:filter 'person :last-name "Duck")
                           (ohm:filter 'person :first-name "Donald")))
(#<PERSON CL-OHM::ID="1" FIRST-NAME="Donald" LAST-NAME="Duck" {1008C6B8F3}>)

1.7Persistence Strategy

Sets, lists and counters are stored implicitly after their mutation. If you change normal attributes (with SETF) then those objects have to be persisted with SAVE.

2API Documentation

See CL-OHM HTML Documentation.

3Running the tests

CL-OHM uses FiveAM for testing. Please installed it with

(ql:quickload :fiveam)

Then you can run the test through ASDF:

(asdf:test-system :cl-ohm)


MIT Copyright (c) 2016 Sebastian Christ

Dependencies (4)

  • alexandria
  • closer-mop
  • cl-redis
  • fiveam

Dependents (0)

    • GitHub
    • Quicklisp