The bdb code is split between controller.lisp and collections.lisp. Controller.lisp: (get-from-root) is implemented using (get-value) (from-root-existsp) uses (existsp) (remove-from-root) uses (remove-kv) (next-oid) uses (db-sequence-get-fixnum) Collections.lisp: (get-value) has a lot of different methods. The root is a bdb-btree so those are the ones that apply. The relevant (get-value) method uses (db-get-key-buffered) (existsp) does too (setf get-value) uses (db-put-buffered) (remove-kv) uses (db-delete-buffered) berkely-db.lisp - lowlevel stuff (db-get-key-buffered) The sql code is in sql-controller.lisp. (sql-get-from-root) uses (sql-get-from-clcn) on collection 0 so does (sql-from-root-existsp) even though there's an (sql-from-clcn-existsp) (sql-remove-from-root) uses (sql-remove-from-clcn) (get-value) has a lot of methods again; the relevant ones are for btrees (get-value) uses (sql-get-from-clcn) on the oid of the btree (existsp) uses (sql-from-clcn-existsp) (setf get-value) uses (sql-add-to-clcn) ... surely this can't be right? (remove-kv) uses (sql-remove-one-from-clcn) (sql-remove-one-from-clcn) Okay, the underlying semantics of (db-delete-buffered) come from db_del(), defined in libsleepycat.c, which wraps around the standard sleepycat db->del(). It's called on the key .... it actually discards all the values... (db-get-key-buffered) uses (%db-get-key-buffered) which uses db_get_raw(). This is defined in libsleepycat.c and simply wraps around the standard sleepycat function db->get(). (db-put-buffered) uses (%db-put-buffered) which uses db_put_raw() which uses db->put(). The flags parameter controls where stuff is inserted... It is ultimately specified by (db-put-buffered) which simply specified AUTO-COMMIT. This means that it overwrites. SO, whenever the bdb backend uses the non-cursor functions there will never be more than one value for the given key, although the API would be happy to let you check anyway. I'm pretty sure that it only uses the cursor functions when its intent is to traverse the entire btree. Now, as for changing stuff. (sql-get-from-clcn) is only used by the btree methods. In fact there are no cursor methods for the sql backend. sql-collections.lisp defines the classes: sql-store-controller sql-btree - has methods for (get-value) etc; used for the root sql-indexed-btree - has methods for (get-value) etc sql-btree-index - doesn't do much Now, (sql-add-to-clcn) is used from several places. It takes a key :insert-only which, if false (the default), tells it to try an update before it tries an insert. (setf get-value) for sql-btree leaves :insert-only default (add-index) for sql-indexed-btree passes :insert-only t (setf get-value) for sql-indexed-btree does two writes writing to the index, it passes :insert-only t writing to the primary, it leaves :insert-only default So what should be happening is that an attempt to set a slot results in a GET, followed by an UPDATE if the slot exists, or an INSERT if it doesn't. I have an object where it does this with one field and not with the other! Hmm.... it can't distinguish between "slot unbound" and "slot bound to nil". This is the cause of the problem: it sees the nil and thinks the slot is unbound, so it inserts instead of appending, which means that ever after there are multiple values with the same key, and the first one gets returned - which is the nil - so it always thinks the slot is unbound, never seeing the newer values. Solution: Change (if (and (not insert-only) (sql-get-from-clcn clcn key sc con)) to (if (and (not insert-only) (sql-from-clcn-existsp clcn key con)) Unfortunately this is not the end of the problem. (slot-boundp) still sees nil as being unbound, and (slot-value) still throws an error when it should return nil. The actual methods to blame are in classes.lisp and metaclasses.lisp: In classes.lisp are :around methods for our favorite MOP functions. Slot-makunbound-using-class is correct as it stands. For the others, the actual semantics are farmed out to macros in metaclasses.lisp: persistent-slot-reader, persistent-slot-writer, and persistent-slot-boundp. These macros.... argh.... have the sleepycat semantics in themselves, but call more functions to handle the "not in a database" and "in an sql database" cases. Those are persistent-slot-boundp-aux, and similar ... oh. I thought those were for slots belonging to the store controller itself. Anyway, they're at the bottom of sql-controller.lisp. The writer uses (sql-add-to-root), so it's already correct. The reader and boundp both need to be changed; reader doesn't call boundp because that would be an extra query. Boundp is the easiest to fix. Hm, I see. I thought each object was its own collection, but actually their slots are all kept as keys under the root. Hm, there's a broken function sql-from-root-existsp which isn't called from anywhere. To fix it, change: (sql-get-from-clcn 0 key con) to (sql-from-clcn-existsp 0 key con) Now, to fix boundp, just change (if (sql-get-from-root (form-slot-key (oid instance) name) sc con ) to (if (sql-from-root-existsp (form-slot-key (oid instance) name) con ) This works. The reader is still broken. I am going to modify the sql-get- functions to return a second value indicating whether there were any values... Actually, sql-get-from-root already tries to do that, but buggily, in that it treats nil as being the same as no value... which rather defeats the entire purpose, since the second value is only necessary to distinguish between nil and no value... So I'll strip out that code and put the semantics directly in sql-get-from-clcn-nth, where they can actually be implemented. The in-bounds check in sql-get-from-clcn-nth is backwards. Fixed. The other fixes are made; now slot-boundp and slot-value work as intended. I see this same bug with treating nil as no-value and pretending to distinguish in the get-value method for sql-btree. I'll fix it there too, but I don't use sql-btrees so I've made the change but it still needs to be tested... no reason it would fail... It's wrong in the (get-value) and (get-primary-key) methods for sql-btree-index, too, so I'm fixing that. Heh, there's a comment "Can this be right?"