Next: Class Indices, Previous: Using Cursors, Up: Tutorial
The indexed-btree class is a BTree which supports secondary
indices. Suppose I store appointments as
* (defclass appointment ()
((date :accessor ap-date :initarg :date :type integer)
(type :accessor ap-type :initarg :type :type string))
(:metaclass persistent-metaclass))
=> #<PERSISTENT-METACLASS APPOINTMENT {481CBBCD}>
and I put them in a table
* (defvar *appointments* (with-transaction () (make-indexed-btree)))
=> *APPOINTMENTS*
keyed by their date (stored as universal-time):
* (defun add-appointment (date type)
(with-transaction ()
(setf (get-value date *appointments*)
(make-instance 'appointment :date date :type type))))
=> ADD-APPOINTMENT
* (add-appointment (encode-universal-time 0 0 0 22 12 2004) "Birthday")
=> #<APPOINTMENT {48A2D29D}>
* (add-appointment (encode-universal-time 0 0 0 14 4 2005) "Birthday")
=> #<APPOINTMENT {48A2D29E}>
* (add-appointment (encode-universal-time 0 0 0 1 1 2005) "Holiday")
=> #<APPOINTMENT {48A2D29F}>
But suppose I would also like to be able to lookup appointments by their type? This is what secondary indices are for.
First, we create a function which generates the secondary key:
* (defun key-by-type (secondary-db primary value)
(declare (ignore secondary-db primary))
(let ((type (ap-type value)))
(when type
(values t type))))
=> KEY-BY-TYPE
It should take three arguments: the secondary index, the primary key,
and the value. It should return two values: T (index it) and
the secondary index, or NIL (don't index it.) Next we add the
index:
* (with-transaction ()
(add-index *appointments* :index-name 'by-type
:key-form 'key-by-type
:populate t))
=> #<BTREE-INDEX {48DFBAA5}>
:index-name is a is a way to refer to the index; symbols are
good for this. :key-form is the way to generate secondary
keys. Note that you need to pass in the symbol bound to a function,
or an actual list defining a lambda, not a function (since we can't
directly serialize functions yet.) Finally :populate tells
Elephant to walk the primary table and generate secondary keys for all
the primary values.
* (defvar *by-type* (get-index *appointments* 'by-type))
=> *by-type*
* (decode-universal-time (ap-date (get-value "Holiday" *by-type*)))
=> 0
0
0
1
1
2005
2
NIL
6
What about conflicting keys? The secondary index (of type
btree-index) supports duplicate keys. You can find the
duplicate entries by using the cursor functions next, next-dup,
next-nodup, prev, prev-nodup:
* (with-btree-cursor (curs *by-type*)
(loop for (more? k v) =
(multiple-value-list (cursor-set curs "Birthday"))
then (multiple-value-list (cursor-next-dup curs))
do
(unless more? (return t))
(multiple-value-bind (s m h d mo y)
(decode-universal-time (ap-date v))
(declare (ignore s m h))
(format t "~A/~A/~A~%" mo d y))))
12/22/2004
4/14/2005
=> T
There are also cursor-p* functions like pcurrent, pnext,
et cetera which also return the primary key. See Cursors.