[elephant-devel] get-instances-by-range should work with string types
Ian Eslick
eslick at csail.mit.edu
Fri Nov 17 14:04:58 UTC 2006
You have to be a little careful because in both BDB and SQL backends
range queries are dependant on the ordering that the database uses for
secondary indices. For instance, there is a C function that handles the
ordering of elements in an index defined on a slot. The idea of a range
query is to gain efficiency by only looking at a subset of objects.
Now you can approximate this by mapping your non-integer objects into an
integer space according to your own ordering. This is provided for in
the (add-class-derived-index ...) which takes a function that is called
by the backend to determine the value. This can be an arbitrary lisp
function. Unless I'm mis-remembering, you can use the name of this
derived index as a 'slotname' when you call (get-instances-by-range ...).
However it may work for strings today because ordering is based on the
order of binary bytes in the serialized representation of lisp data.
I'll see if I can document all this better in the next release; it's
helpful getting all this input to see what is confusing to new users and
what features they want!
Ian
Alain Picard wrote:
> Hello to all Elephant developers, and many words of thanks
> for what appears to be a useful and well thought out library!
>
> I've recently started a project which will (I hope) make some
> good use of Elephant. I'm still wrapping my head around things,
> so go easy on me. As a show of good will, here's my first attempt
> at adding something to elephant.
>
> Suppose you have a class like this:
>
> (defclass test-user ()
> ((name :reader name :initarg :name
> :type string :index t)
> (timestamp :reader timestamp
> :initform (get-universal-time)
> :type integer
> :index t))
> (:metaclass persistent-metaclass))
>
> (defun make-some-users ()
> (let ((n 0)
> (*auto-commit* t))
> (dotimes (i 1000)
> (make-instance 'test-user :name (format nil "User name ~D" (incf n))))
> (get-instances-by-range 'test-user 'name "User name 10" "User name 20")))
>
> (make-some-users)
>
> If you try to do something like
>
> (length (get-instances-by-range 'test-user 'timestamp 0 999999999999))
> ==> 1000
>
> It succeeds.
> But if you try on the name,
>
> (get-instances-by-range 'test-user 'name "User name 10" "User name 11")
> ==>
> Argument X is not a REAL: "User name 10"
> [Condition of type SIMPLE-TYPE-ERROR]
>
> Because the get-instances-by-range function (needlessly) assumes
> that the objects being returned can be compared with numeric
> equality.
>
> This little snippet fixes this problem:
> (change from the 0.6 distribution)
> =============cut================================================
> (defun find-slot-type (class idx-name)
> (flet ((candidate-slot-p (slot)
> (and (eq (type-of slot) 'persistent-effective-slot-definition)
> (slot-value slot 'indexed)
> (eq (slot-definition-name slot) idx-name))))
> (find-if #'candidate-slot-p (class-slots class))))
>
> (defun find-index-comparison-function (class index)
> (let ((type (sb-pcl:slot-definition-type (find-indexed-slot class index))))
> (cond ((subtypep type 'number)
> #'<=)
> ((subtypep type 'string)
> #'string<=)
> (t
> ;; We'll fall back to numerical, though it's not clear to
> ;; me that this is sensible. Maybe should just signal an error,
> ;; and force users to declare :type on all indexed slots?
> #'<=))))
>
> (defmethod get-instances-by-range ((class persistent-metaclass) idx-name start end)
> (let ((comparison (find-index-comparison-function class idx-name)))
> (with-inverted-cursor (cur class idx-name)
> (labels ((next-range (instances)
> (multiple-value-bind (exists? skey val pkey) (cursor-pnext-nodup cur)
> (declare (ignore pkey))
> (if (and exists? (funcall comparison skey end))
> (next-in-range skey (cons val instances))
> (nreverse instances))))
> (next-in-range (key instances)
> (multiple-value-bind (exists? skey val pkey) (cursor-pnext-dup cur)
> (declare (ignore pkey skey))
> (if exists?
> (next-in-range key (cons val instances))
> (progn
> (cursor-pset-range cur key)
> (next-range instances))))))
> (multiple-value-bind (exists? skey val pkey) (cursor-pset-range cur start)
> (declare (ignore pkey))
> (if (and exists? (funcall comparison skey end))
> (next-in-range skey (cons val nil))
> nil))))))
> =============cut================================================
>
> Note that unfortunately FIND-INDEX-COMPARISON-FUNCTION makes
> use of SB-PCL:SLOT-DEFINITION-TYPE; I have not conditionalized
> this for other lisps, nor do I really know what forms are appropriate
> in any other lisps.
>
> Obviously, it is possible to extend this scheme to let users register
> their own comparison functions for more complex types; I'll let you
> judge if this is worth the effort.
>
> Hoping this helps someone, somewhere.
>
> If this was already covered in some other way, then consider this
> message a bug report on the documentation instead. :-)
>
> Alain Picard
>
>
>
More information about the elephant-devel
mailing list