changeset 511:3141e51cb077

Initial checkin of WIP sb-alien bindings. Includes some analogues to libtests/ test files in tests.lisp Coverage of the C API is not complete, but I think there's enough to implement all the same functionality tests as there are in the libtests/ suite.
author mas01cr
date Wed, 21 Jan 2009 21:48:25 +0000
parents 2b95e396f4d7
children a30948382f56
files bindings/sb-alien/interface.lisp bindings/sb-alien/library.lisp bindings/sb-alien/package.lisp bindings/sb-alien/sb-adb.asd bindings/sb-alien/tests.lisp
diffstat 5 files changed, 638 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/sb-alien/interface.lisp	Wed Jan 21 21:48:25 2009 +0000
@@ -0,0 +1,362 @@
+(in-package "SB-ADB")
+
+(defclass adb ()
+  ((alien :initarg alien)))
+(defmethod initialize-instance :after ((o adb) &key)
+  (when (and (slot-boundp o 'alien)
+             (not (null-alien (slot-value o 'alien))))
+    (let ((alien (slot-value o 'alien)))
+      (sb-ext:finalize o (lambda () (%close alien))))))
+
+;;; FIXME: deal with interrupt-safety / leak issues
+
+;;; FIXME: if-does-not-exist.
+(defun open (path &key (direction :input) (if-exists :error) (adb-class 'adb))
+  (flet ((direction-flag (direction)
+           (ecase direction
+             ((:input :probe) sb-posix:o-rdonly)
+             ((:output :io) sb-posix:o-rdwr))))
+    (let* ((truepath (probe-file path))
+           (alien (cond
+                    (truepath
+                     (ecase direction
+                       ((:input :probe) 
+                        (%%open path (direction-flag direction)))
+                       ((:output :io)
+                        (case if-exists
+                          (:error (error "database already exists: ~S" path))
+                          (:append (%open path (direction-flag direction)))
+                          ;; FIXME: not the best implementation of
+                          ;; :SUPERSEDE semantics ever.
+                          (:supersede (delete-file path) (%create path 0 0 0))))))
+                    ((eql direction :input)
+                     (error "database does not exist: ~S" path))
+                    (t (%create path 0 0 0)))))
+      (cond
+        ((null-alien alien)
+         (case direction
+           (:probe nil)
+           (t (error "opening database failed: ~S" path))))
+        (t (make-instance adb-class 'alien alien))))))
+(defmethod close ((o adb))
+  (when (and (slot-boundp o 'alien)
+             (not (null-alien (slot-value o 'alien))))
+    (%close (slot-value o 'alien))
+    (sb-ext:cancel-finalization o)
+    (slot-makunbound o 'alien)))
+(defmacro with-adb ((adb path &rest open-args &key direction adb-class if-exists)
+                    &body body)
+  (declare (ignore direction adb-class if-exists))
+  `(let ((,adb (open ,path ,@open-args)))
+     (unwind-protect
+          (locally ,@body)
+       (close ,adb))))
+
+(defgeneric l2norm (db))
+(defmethod l2norm ((db adb))
+  (%l2norm (slot-value db 'alien)))
+
+(defstruct (datum
+             (:constructor %make-datum)
+             (:constructor 
+              make-datum 
+              (key %data &key times power
+               &aux (data 
+                     (make-array (list (length %data) (length (elt %data 0)))
+                                 :element-type 'double-float 
+                                 :initial-contents %data)))))
+  (key (error "missing argument") :type string)
+  (data (error "missing argument") :type (simple-array double-float (* *)))
+  (times nil :type (or null (simple-array double-float)))
+  (power nil :type (or null (simple-array double-float))))
+
+(defgeneric insert (datum db))
+
+(defmethod insert ((datum datum) (db adb))
+  (let* ((data (datum-data datum))
+         (nvectors (array-dimension data 0))
+         (dim (array-dimension data 1)))
+    (when (datum-times datum)
+      (unless (= (array-total-size (datum-times datum)) (* 2 nvectors))
+        (error "dimension mismatch for times: ~S" datum)))
+    (when (datum-power datum)
+      (unless (= (array-total-size (datum-power datum)) nvectors) 
+        (error "dimension mismatch for power: ~S" datum)))
+    (with-alien ((d adb-datum-t))
+      (sb-sys:with-pinned-objects ((datum-data datum)
+                                   (datum-times datum)
+                                   (datum-power datum))
+        (setf (slot d 'dim) dim)
+        (setf (slot d 'nvectors) nvectors)
+        (setf (slot d 'key) (datum-key datum))
+        (setf (slot d 'data) 
+              (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-data datum))))
+        (if (datum-times datum)
+            (setf (slot d 'times) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
+            (setf (slot d 'times) nil))
+        (if (datum-power datum)
+            (setf (slot d 'power) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
+            (setf (slot d 'power) nil))
+        (sb-int:with-float-traps-masked (:invalid)
+          (%insert-datum (slot-value db 'alien) (addr d)))))))
+
+(defstruct result
+  (key "" :type string)
+  (distance 0d0 :type double-float)
+  (qpos 0 :type (and unsigned-byte fixnum))
+  (ipos 0 :type (and unsigned-byte fixnum)))
+
+;;; Hrm.  To copy (from the malloc heap) or not to copy?  Copying
+;;; would make things generally easier, I guess, and we have to hope
+;;; that the order of magnitude is not such that the copying causes
+;;; pain.
+(defclass results (sequence standard-object)
+  ())
+(defclass copied-query-results (results)
+  ((results :initarg results :accessor %copied-results)))
+(defmethod print-object ((o copied-query-results) s)
+  (pprint-logical-block (s nil)
+    (print-unreadable-object (o s :type t)
+      (format s "(~D results):~2I~@:_" (length o))
+      (sequence:dosequence (r o)
+        (pprint-pop)
+        (format s "~A ~6,3e ~D ~D~@:_"
+                (result-key r) (result-distance r)
+                (result-qpos r) (result-ipos r))))))
+      
+(defmethod sequence:length ((o copied-query-results))
+  (length (%copied-results o)))
+(defmethod sequence:elt ((o copied-query-results) index)
+  (elt (%copied-results o) index))
+(defmethod (setf sequence:elt) (new-value (o copied-query-results) index)
+  (setf (elt (%copied-results o) index) new-value))
+(defmethod sequence:make-sequence-like 
+    ((o copied-query-results) length &rest args 
+     &key initial-element initial-contents)
+  (declare (ignore initial-element initial-contents))
+  (let ((vector (apply #'make-array length args)))
+    (make-instance 'copied-query-results 'results vector)))
+(defmethod sequence:adjust-sequence
+    ((o copied-query-results) length &rest args
+     &key initial-element initial-contents)
+  (declare (ignore initial-element initial-contents))
+  (let ((results (%copied-results o)))
+    (apply #'sequence:adjust-sequence results length args))
+  o)
+
+(defclass proxied-query-results (results)
+  ((adb :initarg adb)
+   (spec :initarg spec)
+   (results :initarg results)))
+(defmethod initialize-instance :after ((o proxied-query-results) &key)
+  (when (and (slot-boundp o 'results)
+             (not (null-alien o))
+             (slot-boundp o 'spec))
+    (with-slots (results spec adb) o
+      (flet ((results-finalizer ()
+               (with-slots (alien) adb
+                 (%free-query-results alien spec results))))
+        (sb-ext:finalize o #'results-finalizer)))))
+
+(defgeneric query (datum db &key))
+
+;;; FIXME: I don't like this way of generalizing the boilerplate;
+;;; isn't there a nice functional way of doing this?
+(macrolet 
+    ((def (name datum-class &body qdatum-forms)
+       `(defmethod ,name ((datum ,datum-class) (db adb) &key 
+                          (sequence-length 1) (sequence-start 0)
+                          exhaustivep accumulation distance
+                          ;; FIXME: dubious historical defaults
+                          (npoints 10) (ntracks 10)
+
+                          (radius nil radiusp)
+                          (include-keys nil include-keys-p)
+                          (exclude-keys nil exclude-keys-p))
+          (unless (slot-boundp db 'alien)
+            (error "database ~S is closed" db))
+         (with-alien ((qid adb-query-id-t)
+                      (qparams adb-query-parameters-t)
+                      (qrefine adb-query-refine-t)
+                      (qdatum adb-datum-t))
+           ,@qdatum-forms
+           (setf (slot qid 'datum) (addr qdatum))
+           (setf (slot qid 'sequence-length) sequence-length)
+           (setf (slot qid 'sequence-start) sequence-start)
+           (setf (slot qid 'flags) (if exhaustivep 1 0))
+
+           (setf (slot qparams 'accumulation)
+                 (ecase accumulation
+                   (:db 1)
+                   (:per-track 2)
+                   (:one-to-one 3)))
+           (setf (slot qparams 'distance)
+                 (ecase distance
+                   (:dot-product 1)
+                   (:euclidean-normed 2)
+                   (:euclidean 3)))
+           (setf (slot qparams 'npoints) (or npoints 0))
+           (setf (slot qparams 'ntracks) (or ntracks 0))
+
+           (let ((refine-flags 0))
+             (when radiusp 
+               (setf refine-flags (logior refine-flags 4))
+               (setf (slot qrefine 'radius) (float radius 0d0)))
+             ;; FIXME: the freeing of the KEYS slot in these
+             ;; include/exclude keylists isn't interrupt-safe.
+             ;;
+             ;; FIXME: think quite hard about the behaviour of this
+             ;; when LENGTH is 0.
+             (when include-keys-p
+               (setf refine-flags (logior refine-flags 1))
+               (let ((length (length include-keys)))
+                 (setf (slot (slot qrefine 'include) 'nkeys) length)
+                 (let ((keys (make-alien c-string length)))
+                   (setf (slot (slot qrefine 'include) 'keys) keys)
+                   (loop for key being the elements of include-keys
+                         for i upfrom 0
+                         do (setf (deref keys i) key)))))
+             (when exclude-keys-p
+               (setf refine-flags (logior refine-flags 2))
+               (let ((length (length exclude-keys)))
+                 (setf (slot (slot qrefine 'exclude) 'nkeys) length)
+                 (let ((keys (make-alien c-string length)))
+                   (setf (slot (slot qrefine 'exclude) 'keys) keys)
+                   (loop for key being the elements of exclude-keys
+                         for i upfrom 0
+                         do (setf (deref keys i) key)))))
+             (setf (slot qrefine 'flags) refine-flags))
+           (setf (slot qrefine 'hopsize) 1)
+           
+           ;; FIXME: hm, this possibly suggests that there's something
+           ;; a bit wrong with the C audioDB interface.  The API
+           ;; currently exposed effectively requires either that all
+           ;; the processing of query results happens in the same
+           ;; dynamic extent as the call to audiodb_query_spec(), or
+           ;; that the adb_query_spec_t object is allocated on the
+           ;; heap.  We need to think harder about whether the spec
+           ;; argument is really required (I think it probably isn't).
+           ;;
+           ;; meanwhile, here we're using it with dynamic extent anyway, so
+           ;; we could put it right back on the stack.
+           (let ((qspec (make-alien adb-query-spec-t)))
+             (unwind-protect
+                  (progn
+                    (setf (slot qspec 'qid) qid)
+                    (setf (slot qspec 'params) qparams)
+                    (setf (slot qspec 'refine) qrefine)
+                    
+                    (let ((results 
+                           (sb-int:with-float-traps-masked (:invalid)
+                             (%query (slot-value db 'alien) qspec))))
+                      (flet ((collect-copied-results ()
+                               (let ((nresults (slot results 'nresults))
+                                     (cresults (slot results 'results)))
+                                 (coerce 
+                                  (loop for i below nresults
+                                        for r = (deref cresults i)
+                                        collect (make-result 
+                                                 :key (slot r 'key) 
+                                                 :distance (slot r 'dist)
+                                                 :qpos (slot r 'qpos) 
+                                                 :ipos (slot r 'ipos)))
+                                  'vector))))
+                        (unwind-protect
+                             (make-instance 'copied-query-results
+                                            'results (collect-copied-results))
+                          (%free-query-results (slot-value db 'alien) qspec results)))))
+               (when (logbitp 0 (slot (slot qspec 'refine) 'flags))
+                 (free-alien (slot (slot (slot qspec 'refine) 'include) 'keys)))
+               (when (logbitp 1 (slot (slot qspec 'refine) 'flags))
+                 (free-alien (slot (slot (slot qspec 'refine) 'exclude) 'keys)))
+               (free-alien qspec)))))))
+  (def query string (setf (slot qdatum 'key) datum))
+  (def query datum 
+    (setf (slot qdatum 'key) (datum-key datum))
+    (setf (slot qdatum 'dim) (array-dimension (datum-data datum) 1))
+    (setf (slot qdatum 'nvectors) (array-dimension (datum-data datum) 0))
+    (setf (slot qdatum 'data) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-data datum))))
+    (if (datum-times datum)
+        (setf (slot qdatum 'times) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
+        (setf (slot qdatum 'times) nil))
+    (if (datum-power datum)
+        (setf (slot qdatum 'power) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
+        (setf (slot qdatum 'power) nil))))
+
+#+test
+(sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb")
+  (sb-adb:query "KSA_CHARM_337" db :exhaustivep t :sequence-length 30 
+                :accumulation :per-track :distance :euclidean :npoints 1 :ntracks 20))
+
+#+test
+(sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb")
+  (sb-adb:query "KSA_CHARM_337" db :sequence-start 20 :sequence-length 20
+                :accumulation :per-track :distance :euclidean-normed
+                :npoints 10 :ntracks 1))
+
+#+test
+(sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb")
+  (sb-adb:query "KSA_CHARM_337" db 
+                :exhaustivep t :sequence-length 30
+                :accumulation :per-track :distance :euclidean-normed 
+                :npoints 2 :ntracks 10))
+
+;;; only hacks and tests below
+#|
+(defun foo ()
+  (let ((db (%open "/home/csr21/tmp/omras2-workshop/9.adb" sb-posix:o-rdonly)))
+    (unless (null-alien db)
+      (unwind-protect
+           (with-alien ((status adb-status-t))
+           (%status db (addr status))
+           (print (list (slot status 'dim) (slot status 'nfiles))))
+        (%close db)))))
+
+(defun set-up-spec (spec qid qparams qrefine)
+  (declare (type (alien adb-query-parameters-t) qparams)
+           (type (alien adb-query-refine-t) qrefine)
+           (type (alien adb-query-id-t) qid)
+           (type (alien adb-query-spec-t) spec))
+  (setf (slot spec 'refine) qrefine)
+  nil)
+
+(defun bar ()
+  (let ((db (%open "/home/csr21/tmp/omras2-workshop/9.adb" sb-posix:o-rdonly)))
+    (unless (null-alien db)
+      (unwind-protect
+           (with-alien ((qid adb-query-id-t)
+                        (qparams adb-query-parameters-t)
+                        (qrefine adb-query-refine-t)
+                        (qspec adb-query-spec-t)
+                        (datum adb-datum-t))
+             (setf (slot datum 'key) "KSA_CHARM_337")
+             (setf (slot datum 'data) (sap-alien (sb-sys:int-sap 0) (* double)))
+             
+             (setf (slot qid 'datum) (addr datum))
+             (setf (slot qid 'sequence-length) 30)
+             (setf (slot qid 'flags) 1) ; ADB_QID_FLAG_EXHAUSTIVE
+             
+             (setf (slot qparams 'accumulation) 2) ; ADB_ACCUMULATION_PER_TRACK
+             (setf (slot qparams 'distance) 2) ; ADB_DISTANCE_EUCLIDEAN_NORMED
+             (setf (slot qparams 'npoints) 1)
+             (setf (slot qparams 'ntracks) 20)
+             
+             (setf (slot qrefine 'flags) 0)
+             (setf (slot qrefine 'hopsize) 1)
+             
+             (setf (slot qspec 'qid) qid)
+             (setf (slot qspec 'params) qparams)
+             (setf (slot qspec 'refine) qrefine)
+             (let ((results (%query db (addr qspec))))
+               (unless (null-alien results)
+                 (unwind-protect
+                      (flet ((print-result (n)
+                               (let ((result (deref (slot results 'results) n)))
+                                 (format t "~&~A ~F ~D ~D~%"
+                                         (slot result 'key) (slot result 'dist)
+                                         (slot result 'qpos) (slot result 'ipos)))))
+                        (dotimes (i (slot results 'nresults))
+                          (print-result i)))
+                   (%free-query-results db (addr qspec) results)))))
+        (%close db)))))
+|#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/sb-alien/library.lisp	Wed Jan 21 21:48:25 2009 +0000
@@ -0,0 +1,130 @@
+(in-package "SB-ADB")
+
+(defmacro define-int-checking-function (name arglist llname)
+  `(defun ,name ,arglist
+     (let ((result (,llname ,@arglist)))
+       (unless (eql 0 result)
+         (error "~S on ~{~S ~}failed." ',name (list ,@arglist))))))
+
+(defmacro define-pointer-checking-function (name arglist llname)
+  `(defun ,name ,arglist
+     (let ((result (,llname ,@arglist)))
+       (when (null-alien result)
+         (error "~S on ~{~S ~}failed." ',name (list ,@arglist)))
+       result)))
+
+(define-alien-type adb-t
+  (struct adb))
+
+(define-alien-routine ("audiodb_open" %%open) (* adb-t)
+  (path c-string)
+  (flags int))
+(define-pointer-checking-function %open (path flags) %%open)
+
+(define-alien-routine ("audiodb_create" %%create) (* adb-t)
+  (path c-string)
+  (datasize (unsigned 32))
+  (ntracks (unsigned 32))
+  (datadim (unsigned 32)))
+(define-pointer-checking-function %create (path datasize ntracks datadim) 
+  %%create)
+
+(define-alien-routine ("audiodb_l2norm" %%l2norm) int
+  (adb (* adb-t)))
+(define-int-checking-function %l2norm (adb) %%l2norm)
+
+(define-alien-routine ("audiodb_power" %%power) int
+  (adb (* adb-t)))
+(define-int-checking-function %power (adb) %%power)
+
+(define-alien-type adb-datum-t
+  (struct adb-datum
+    (nvectors (unsigned 32))
+    (dim (unsigned 32))
+    (key c-string)
+    (data (* double))
+    (power (* double))
+    (times (* double))))
+
+(define-alien-routine ("audiodb_insert_datum" %%insert-datum) int
+  (adb (* adb-t))
+  (datum (* adb-datum-t)))
+(define-int-checking-function %insert-datum (adb datum) %%insert-datum)
+
+(define-alien-type adb-status-t
+  (struct adb-status
+    (nfiles (unsigned 32))
+    (dim (unsigned 32))
+    (ignore1 (unsigned 32))
+    (ignore2 (unsigned 32))
+    (flags (unsigned 32))
+    (length (unsigned 64))
+    (data-region-size (unsigned 64))))
+
+(define-alien-routine ("audiodb_status" %%status) int
+  (adb (* adb-t))
+  (status (* adb-status-t)))
+(define-int-checking-function %status (adb datum) %%status)
+
+(define-alien-type adb-query-id-t
+  (struct adbqueryid
+    (datum (* adb-datum-t))
+    (sequence-length (unsigned 32))
+    (flags (unsigned 32))
+    (sequence-start (unsigned 32))))
+
+(define-alien-type adb-query-parameters-t
+  (struct adbqueryparameters
+    (accumulation (unsigned 32))
+    (distance (unsigned 32))
+    (npoints (unsigned 32))
+    (ntracks (unsigned 32))))
+
+(define-alien-type adb-keylist-t
+  (struct adbkeylist
+    (nkeys (unsigned 32))
+    (keys (* c-string))))
+
+(define-alien-type adb-query-refine-t
+  (struct adbqueryrefine
+    (flags (unsigned 32))
+    (include adb-keylist-t)
+    (exclude adb-keylist-t)
+    (radius double)
+    (absolute-threshold double)
+    (relative-threshold double)
+    (duration-ratio double)
+    (hopsize (unsigned 32))))
+
+(define-alien-type adb-query-spec-t
+  (struct adbqueryspec
+    (qid adb-query-id-t)
+    (params adb-query-parameters-t)
+    (refine adb-query-refine-t)))
+
+(define-alien-type adb-result-t
+  (struct adbresult
+    (key c-string)
+    (dist double)
+    (qpos (unsigned 32))
+    (ipos (unsigned 32))))
+
+(define-alien-type adb-query-results-t
+  (struct adbqueryresults
+    (nresults (unsigned 32))
+    (results (* adb-result-t))))
+
+(define-alien-routine ("audiodb_query_spec" %%query) (* adb-query-results-t)
+  (adb (* adb-t))
+  (spec (* adb-query-spec-t)))
+(define-pointer-checking-function %query (adb spec) %%query)
+
+(define-alien-routine ("audiodb_query_free_results" %%free-query-results) int
+  (adb (* adb-t))
+  (spec (* adb-query-spec-t))
+  (results (* adb-query-results-t)))
+(define-int-checking-function %free-query-results (adb spec results)
+  %%free-query-results)
+
+(define-alien-routine ("audiodb_close" %close) void
+  (adb (* adb-t)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/sb-alien/package.lisp	Wed Jan 21 21:48:25 2009 +0000
@@ -0,0 +1,4 @@
+(cl:defpackage "SB-ADB"
+  (:use "CL" "SB-ALIEN")
+  (:export "ADB" "OPEN" "CLOSE" "QUERY" "WITH-ADB")
+  (:shadow "OPEN" "CLOSE"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/sb-alien/sb-adb.asd	Wed Jan 21 21:48:25 2009 +0000
@@ -0,0 +1,6 @@
+(asdf:defsystem :sb-adb
+  :serial t
+  :depends-on (sb-posix)
+  :components ((:file "package")
+               (:file "library")
+               (:file "interface")))
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/sb-alien/tests.lisp	Wed Jan 21 21:48:25 2009 +0000
@@ -0,0 +1,136 @@
+(in-package "SB-ADB")
+
+(define-symbol-macro %default-query-args nil)
+(defmacro with-default-query-args (args &body body &environment env)
+  (let ((current-args (macroexpand '%default-query-args env)))
+    `(symbol-macrolet ((%default-query-args ,(append args current-args)))
+       ,@body)))
+
+(defmacro with-query-result-assertions 
+    ((query db &rest query-args) &body body &environment env)
+  (let ((results (gensym "RESULTS"))
+        (default-args (macroexpand '%default-query-args env)))
+    `(let* ((,results (query ,query ,db ,@query-args ,@default-args))
+            (length (length ,results)))
+       (flet ((%present (list)
+                (find-if (lambda (r)
+                           (and
+                            (string= (first list) (result-key r))
+                            (< (abs (- (second list) (result-distance r))) 1e-4)
+                            (= (third list) (result-qpos r))
+                            (= (fourth list) (result-ipos r))))
+                         ,results)))
+         (declare (ignorable #'%present))
+         (macrolet ((present (&rest forms)
+                      `(and ,@(loop for f in forms collect `(%present ',f)))))
+           ,@(loop for b in body collect `(assert ,b)))))))
+
+(defmacro with-asserted-query-results ((query db &rest query-args) &body body)
+  `(with-query-result-assertions (,query ,db ,@query-args)
+     (= length ,(length body))
+     (present ,@body)))
+
+(declaim (optimize debug))
+
+(defun test-0003 ()
+  (let ((datum (make-datum "testfeature" '((1d0)))))
+    (with-adb (db "testdb.0003" :direction :output :if-exists :supersede)
+      (l2norm db)
+      (insert datum db)
+      (with-asserted-query-results
+          (datum db :npoints 10 :accumulation :db :distance :dot-product)
+        ("testfeature" 1 0 0)))))
+
+(defun test-0004 ()
+  (let ((feature (make-datum "testfeature" '((0d0 1d0) (1d0 0d0))))
+        (query05 (make-datum "testquery" '((0d0 0.5d0))))
+        (query50 (make-datum "testquery" '((0.5d0 0d0)))))
+    (with-adb (db "testdb.0004" :direction :output :if-exists :supersede)
+      (l2norm db)
+      (insert feature db)
+      (with-default-query-args (:accumulation :db :distance :dot-product)
+        (with-asserted-query-results (query05 db :npoints 10)
+          ("testfeature" 0.5 0 0) ("testfeature" 0 0 1))
+        (with-asserted-query-results (query05 db :npoints 1)
+          ("testfeature" 0.5 0 0))
+        (with-asserted-query-results (query50 db :npoints 10)
+          ("testfeature" 0.5 0 1) ("testfeature" 0 0 0))
+        (with-asserted-query-results (query50 db :npoints 1)
+          ("testfeature" 0.5 0 1))))))
+
+(defun test-0010 ()
+  (let ((feature01 (make-datum "testfeature01" '((0d0 1d0))))
+        (feature10 (make-datum "testfeature10" '((1d0 0d0))))
+        (query05 (make-datum "testquery" '((0d0 0.5d0))))
+        (query50 (make-datum "testquery" '((0.5d0 0d0)))))
+    (with-adb (db "testdb.0010" :direction :output :if-exists :supersede)
+      (insert feature01 db)
+      (insert feature10 db)
+      (l2norm db)
+      (with-default-query-args
+          (:accumulation :per-track :ntracks 10 :npoints 10 :distance :euclidean-normed)
+        (with-asserted-query-results (query05 db)
+          ("testfeature01" 0 0 0) ("testfeature10" 2 0 0))
+        (with-asserted-query-results (query05 db :radius 5)
+          ("testfeature01" 0 0 0) ("testfeature10" 2 0 0))
+        (with-asserted-query-results (query05 db :radius 1)
+          ("testfeature01" 0 0 0))
+        (with-asserted-query-results (query50 db)
+          ("testfeature01" 2 0 0) ("testfeature10" 0 0 0))
+        (with-asserted-query-results (query50 db :radius 5)
+          ("testfeature01" 2 0 0) ("testfeature10" 0 0 0))
+        (with-asserted-query-results (query50 db :radius 1)
+          ("testfeature10" 0 0 0))))))
+
+(defun test-0031 ()
+  (let ((feature01 (make-datum "testfeature01" '((0d0 1d0))))
+        (feature10 (make-datum "testfeature10" '((1d0 0d0))))
+        (query05 (make-datum "testquery" '((0d0 0.5d0)))))
+    (with-adb (db "testdb.0031" :direction :output :if-exists :supersede)
+      (insert feature01 db)
+      (insert feature10 db)
+      (l2norm db)
+      (with-default-query-args 
+          (:accumulation :per-track :ntracks 10 :npoints 10 :distance :euclidean-normed)
+        (with-asserted-query-results (query05 db)
+          ("testfeature01" 0 0 0) ("testfeature10" 2 0 0))
+        (with-asserted-query-results (query05 db :include-keys ()))
+        (with-asserted-query-results (query05 db :include-keys '("testfeature01"))
+          ("testfeature01" 0 0 0))
+        (with-asserted-query-results (query05 db :include-keys '("testfeature10"))
+          ("testfeature10" 2 0 0))
+        (with-asserted-query-results (query05 db :include-keys '("testfeature10" "testfeature01"))
+          ("testfeature01" 0 0 0) ("testfeature10" 2 0 0))
+
+        (with-asserted-query-results (query05 db :exclude-keys '("testfeature10" "testfeature01")))        
+
+        (with-asserted-query-results (query05 db :exclude-keys '("testfeature01"))
+          ("testfeature10" 2 0 0))
+        (with-asserted-query-results (query05 db :exclude-keys '("testfeature10"))
+          ("testfeature01" 0 0 0))
+        (with-asserted-query-results (query05 db :exclude-keys ())
+          ("testfeature01" 0 0 0) ("testfeature10" 2 0 0))))))
+
+(defun test-0036 ()
+  (let ((feature01 (make-datum "testfeature01" '((0d0 1d0) (1d0 0d0))))
+        (feature10 (make-datum "testfeature10" '((1d0 0d0) (0d0 1d0))))
+        (query05 (make-datum "testquery" '((0d0 0.5d0))))
+        (query50 (make-datum "testquery" '((0.5d0 0d0)))))
+    (with-adb (db "testdb.0036" :direction :output :if-exists :supersede)
+      (insert feature01 db)
+      (insert feature10 db)
+      (l2norm db)
+      (with-default-query-args 
+          (:accumulation :per-track :ntracks 10 :distance :euclidean-normed)
+        (dolist (npoints '(10 2 5))
+          (with-asserted-query-results (query05 db :npoints npoints)
+            ("testfeature01" 0 0 0) ("testfeature01" 2 0 1)
+            ("testfeature10" 0 0 1) ("testfeature10" 2 0 0)))
+        (with-asserted-query-results (query05 db :npoints 1)
+          ("testfeature01" 0 0 0) ("testfeature10" 0 0 1))
+        (dolist (npoints '(10 2 5))
+          (with-asserted-query-results (query50 db :npoints npoints)
+            ("testfeature01" 0 0 1) ("testfeature01" 2 0 0)
+            ("testfeature10" 0 0 0) ("testfeature10" 2 0 1)))
+        (with-asserted-query-results (query50 db :npoints 1)
+          ("testfeature01" 0 0 1) ("testfeature10" 0 0 0))))))