Warning: Can't synchronize with the repository (Unsupported version control system "darcs": Can't find an appropriate component, maybe the corresponding plugin was not enabled? ). Look in the Trac log for more information.

Statice was a late 1980's design for a persistent object system for lisp. Instead of a proper persistent object extension of CLOS, Statice defined a parallel object system based on persistent schemas (classes) consisting of attributes (slots). Instantiated schemas are called entities (instead of CLOS instances).

Schemas

There were a plethora of attribute options to help queries and indexing:

  • :unique -- is the value of the slot unique (boolean)
  • :no-nulls -- is the slot guaranteed to have no nulls? (boolean)
  • :cached -- When objects are pulled from the database, should slot values be cached (snapshot of values). This improves performance of access outside transactions but does not guarantee consistency of read operations.
  • :inverse -- the name of an inverse operator that finds an object based on a slot value
  • :inverse-index -- Whether or not to do an index on this slot
  • :read-only -- no writer is defined, cannot use setf, slot value is permanent and static

They supported set-valued attributes (add-to-set and delete-from-set.

Each attribute had pre-defined types. This helps query performance and correctness and storage efficiency, but requires more careful planning of what is stored.

Types included: integer, integer subrange, string, limited-string, boolean, float, double, number, symbol, entity-handle (instance pointer), t (any object), member of set, time, time-interval and a few others not worth mentioning.

Queries

Queries use with-transaction semantics to elephant. Associative access is supported via the for-each operator.

(for-each ((i instructor)
           (:where (and (equal "Full" (instructor-rank i))
                        (string-greater-p (person-name i) string))))
  <body-progn>)

Predicates correspond to lisp predicates. Conjunctions are supported via 'and' and constraints on attributes are specified as readers applied to the declared instance variable 'i'. For-each has map semantics.

Clauses in the :where expression have a form similar to that described on the Elephant QueryLanguage page.

(comparison form (reader-function variable))

These can be interchanged for operators like equalp, but order matters for operators like string-greaterp. typep has a type expression instead of (reader-function variable).

A partial list of supported conditions are: = != < > <= >= any eq eql equal string-prefix string-prefix-exact string-search string-search-exact string-equal string-greaterp string-lessp string-not-greaterp string-not-lessp string= string!= string< string> string<= string>= typep

It should be noted that strings can be exact ('Apple' > 'apple') or inexact ('Apple' = 'apple'). Elephant does not support this currently because slots are not statically typed.

More sophisticated queries can use:

  • :count -- only call the body on the first n objects
  • :order-by -- Standard sorting prior to looping (:order-by (reader-fn var) order) which requires knowing the type.

Joins are performed by declaring multiple entities in the header.

(for-each ((i instructor) (d department))
          (:where (and (eq d (instructor-dept i))
                       (eq (department-head d)
                           (person-named head))))
   (push (person-name i) instructors))