> After studying the question a bit more, I realize that there is good > reason for the C API to separate statement preparation from statement > binding; preparation does all the parsing and compiling to bytecode, > which is one of the more expensive parts of sending a query, whereas > binding substitutes values for the parameters. > > As I don't want to expose the prepared-statement foreign objects in the > API, a practical (as opposed to POC) implementation will need to provide > a statement cache. Most SQLite interfaces do this, but it is not part > of SQLite itself. The cache is basically a bounded-size mapping that > maps SQL strings (or hashes of them) to the internal prepared-statement > objects. When the cache is full, an element is kicked out and the > corresponding prepared-statement object is freed. > > So when we call -exec/-eval, the cache is searched; on a miss, we > prepare the statement and add it to the cache. Then it is bound and > executed. So a table where SQL strings are weak references pointing to sqlite_stmt foreign objects? And it's probably good enough to compare the strings using eq?. If you want to optimize for speed, then (sql-do `("mumble @a @b @c mumble" a ,a b ,b c ,c)) is a bit wasteful as well. SQLite uses positional parameters internally, and the user must convert named parameters to positional ones using sqlite3_bind_parameter_index(). If the statement stays the same, then the name->position mapping stays the same as well and should be cached. Further, destructuring an alist/plist on every call to unpack parameter values is unnecessary effort.