[elephant-devel] Start of 0.6.1 Beta Cycle

Ian Eslick eslick at csail.mit.edu
Wed Mar 21 19:30:40 UTC 2007


Henrik,

I think there's a confusion here because we have not properly  
communicated the semantics we are intending for map-index.  You have  
properly identified a problem in mine (inability to traverse null  
values) which I introduced to solve a problem I anticipated that  
shows up under your proposal.

I implemented map-index by using 'null' values of start and end to  
mean 'first' and 'last'.  Under this contract the current tests are  
correct.  However, this use of null precludes indexing over actual  
null values which is a specification bug.  You've fixed that by  
handling the presence or absence of the start/end keywords.  That  
makes functions that wrap map-index more complex to write.

For example, how do we want to have users specify queries such as:  
"all ages < 20" in get-instances-by-range?  (which is based on map- 
index).  We'd have to add keyword arguments or the 'null' contract to  
it.  The current implementation is:

(defun get-instances-by-range (class slot start end)
   ...
   (map-index #'collector slot-index :start start :end end))

In this case I force the user to use nil to indicate 'first' and  
'last' and we should find an alternative to that in case people want  
to iterate over booleans (although thye could use by-value for nil  
and then for t).  map-index needs a fix to provide get-instances-by- 
value for nulls, but not necessarily the current contract it creates  
in get-instances-by-range.

Under your map-index proposal, if get-instances-by-range wants to  
implement the null contract you would have to do something like the  
following:

(defun get-instances-by-range (class slot start end)
   ...
   (case ((and (null start) (null end))
	 (map-index #'collector slot-index))
         ((null start)
          (map-index #'collector slot-index :end end))
         ((null end)
          (map-index #'collector slot-index :start start))
         (t
          (map-index #'collector slot-index :start start :end end))))

Or something that computes the keyword list and then uses apply,  
which isn't very convenient either.  Users may also write their own  
query functions that utilize map-index and would have to reproduce  
this complex call to map-index syntax.

I propose a third alternative for map-index:

(defun map-index (fn index &key start end first last)
    ...)

Where first and last are booleans that override any values in start  
and end and mean the first value and last value in the ordered  
index.  Thus our implementation becomes:

(defun get-instances-by-range (class slot start end)
   ...
   (map-index #'collector slot-index :start start :end end :first  
(null start) :last (null start)))

or to use :first and :last

(defun get-instances-by-range (class slot start end)
   ...
   (map-index #'collector slot-index :start start :end end :first (eq  
start :first) :last (eq end :last)))

What do you think about the map-index interface and what should we do  
about first and last range queries in get-instances-by-range?

One alternative is to also add :first and :last keywords to get- 
instances-by-range so we don't introduce any special values to the  
arguments start and end.

Ian

On Mar 21, 2007, at 1:58 PM, Henrik Hjelte wrote:

> Great work.
> It seems that one of my changes yesterday introduced a bug, so Ian  
> fixed
> that bug and my change disappeared in the process. It suits me right,
> because I really should have made a testcase showing the bug before
> fixing it. Better later than never though.
>
> I modify the test indexing-basic in testindexing.lisp:
> (get-instances-by-value 'idx-one 'slot1 nil) should return zero
> instances, right? Now it returns three which is wrong.
>
> I had to modify the map-index method in collections.lisp some more to
> get this right without breaking other tests. Get-instances-by-value
> calls collections with start equal to end, so I do a special check for
> this, and that also makes it possible to use the cursor-pset function
> instead of cursor-pset-range which could be a speedup at least in
> theory.
>
> /Henrik Hjelte
>
> Changes:
> {
> hunk ./tests/testindexing.lisp 101
> -               (length (get-instances-by-range 'idx-one 'slot1 n (+ 1
> n))))
> +               (length (get-instances-by-range 'idx-one 'slot1 n (+ 1
> n)))
> +                (length (get-instances-by-value 'idx-one 'slot1  
> nil)))
> hunk ./tests/testindexing.lisp 104
> -  3 2 1 t 3)
> +  3 2 1 t 3 0)
> hunk ./src/elephant/collections.lisp 377
> -                          (next-range))))))
> +                          (next-range)))))
> +                 (same-start-and-end ()
> +                   (when (and start-supplied-p end-supplied-p)
> +                     (or (and (null start) (null end))
> +                         (and start end (lisp-compare<= start end)
> (lisp-compare<= end start))))))
> hunk ./src/elephant/collections.lisp 384
> -             (if (and start-supplied-p (not (null start)))
> -                 (cursor-pset-range cur start)
> -                 (cursor-pfirst cur))
> +              (cond
> +                ((same-start-and-end)
> +                 (cursor-pset cur start))
> +                ((and start-supplied-p (not (null start)))
> +                 (cursor-pset-range cur start))
> +                (t (cursor-pfirst cur)))
> }
>
> The whole new map-index method:
>
> (defmethod map-index (fn (index btree-index) &rest args &key (start  
> nil
> start-supplied-p) (end nil end-supplied-p))
>   "Like map-btree, but takes a function of three arguments key, value
> and primary key
>    if you want to get at the primary key value, otherwise use map- 
> btree"
>   (declare (dynamic-extent args)
> 	   (ignorable args))
>   (let ((sc (get-con index)))
>     (ensure-transaction (:store-controller sc)
>       (with-btree-cursor (cur index)
> 	(labels ((next-range ()
> 		   (multiple-value-bind (exists? skey val pkey) (cursor-pnext-nodup
> cur)
> 		     (if (and exists?
> 			      (or (not end-supplied-p)
> 				  (null end)
> 				  (lisp-compare<= skey end)))
> 		       (progn
> 			 (funcall fn skey val pkey)
> 			 (next-in-range skey))
> 		       (return-from map-index nil))))
> 		 (next-in-range (key)
> 		   (multiple-value-bind (exists? skey val pkey) (cursor-pnext-dup  
> cur)
> 		     (if exists?
> 			 (progn
> 			   (funcall fn skey val pkey)
> 			   (next-in-range key))
> 			 (progn
> 			   (cursor-pset-range cur key)
> 			   (next-range)))))
>                  (same-start-and-end ()
>                    (when (and start-supplied-p end-supplied-p)
>                      (or (and (null start) (null end))
>                          (and start end (lisp-compare<= start end)
> (lisp-compare<= end start))))))
> 	  (declare (dynamic-extent next-range next-in-range))
> 	  (multiple-value-bind (exists? skey val pkey)
>               (cond
>                 ((same-start-and-end)
>                  (cursor-pset cur start))
>                 ((and start-supplied-p (not (null start)))
>                  (cursor-pset-range cur start))
>                 (t (cursor-pfirst cur)))
> 	    (if (and exists?
> 		     (or (not end-supplied-p)
> 			 (null end)
> 			 (lisp-compare<= skey end)))
> 		(progn
> 		  (funcall fn skey val pkey)
> 		  (next-in-range skey))
> 		nil)))))))
>
>
>
> _______________________________________________
> elephant-devel site list
> elephant-devel at common-lisp.net
> http://common-lisp.net/mailman/listinfo/elephant-devel




More information about the elephant-devel mailing list