#lang scribble/manual @title{Keystore} @defmodule[keystore] A small persistent key-value store backed by SQLite. Keys and values may be arbitrary Racket values and are stored using transparent serialization. @section{Overview} The keystore provides persistent storage with automatic serialization and deserialization. Keys are additionally stored in a stringified lowercase form, which allows glob-style queries. @section{Structure} @defstruct*[keystore ([file any/c] [path path?] [dbh any/c] [lock-sem semaphore?] [in-lock (or/c thread? #f)] [entered exact-nonnegative-integer?])]{ Represents an open keystore. The @racket[file] field contains the original argument, @racket[path] is the resolved database path, and @racket[dbh] is the SQLite connection. The remaining fields implement the reentrant lock used by @racket[ks-with-lock]: @racket[lock-sem] serializes access, @racket[in-lock] records the thread that holds the lock, and @racket[entered] records that thread's nesting depth. The @racket[in-lock] and @racket[entered] fields are mutable internal state. The @tt{keystore?} predicate is altered to also check if the database connection is (still) valid. } @defproc[(keystore? [ks any/c]) boolean?]{ Returns #t, if ks is a valid keystore. Returns #f, otherwise. Note. When a previously opened keystore (which is valid), is closed with @tt{ks-close}, keystore? will return #f, because the keystore is not valid anymore. } @section{Opening & Closing} @defproc[(ks-open [file (or/c path? string? symbol?)]) keystore?]{ Opens or creates a keystore. When @racket[file] is a symbol, a cache location is used; otherwise it is interpreted as a filesystem path. The database schema is created automatically if it does not yet exist. } @defproc[(ks-close [ks keystore?]) boolean?]{ Closes the keystore. The keystore handle is invalidated after closing the underlying storage (database). } @section{Basic Operations} @defproc[(ks-set! [ks keystore?] [key any/c] [value any/c]) boolean?]{ Stores @racket[value] under @racket[key], replacing any existing value. The function always returns @racket[#t]. } @defproc[(ks-get [ks keystore?] [key any/c] [default any/c] ...) any/c]{ Retrieves the value associated with @racket[key]. If the key is not present, the provided default value is returned when given; otherwise the symbol @racket['ks-nil] is returned. } @defproc[(ks-exists? [ks keystore?] [key any/c]) boolean?]{ Returns @racket[#t] if the key exists, and @racket[#f] otherwise. } @defproc[(ks-key-count [ks keystore?]) number?]{ Returns the number of keys in the keystore. } @defproc[(ks-drop! [ks keystore?] [key any/c]) boolean?]{ Removes the key from the store. The function always returns @racket[#t]. } @section{Enumeration} @defproc[(ks-keys [ks keystore?]) (listof any/c)]{ Returns all keys in the store. } @defproc[(ks-key-values [ks keystore?]) (listof (cons/c any/c any/c))]{ Returns all key-value pairs as cons cells. } @section{Glob Queries} Glob queries operate on a lowercase string representation of keys. @defproc[(ks-keys-glob [ks keystore?] [pattern string?]) (listof any/c)]{ Returns all keys whose string form matches @racket[pattern]. } @defproc[(ks-key-values-glob [ks keystore?] [pattern string?]) (listof (cons/c any/c any/c))]{ Returns key-value pairs whose keys match @racket[pattern]. } @section{Raw Access} @defproc[(ks-keys-raw [ks keystore?]) list?]{ Returns raw key rows in the form: @racketblock[ (list key-string str-key) ] } @defproc[(ks-key-values-raw [ks keystore?]) list?]{ Returns raw key-value rows in the form: @racketblock[ (list key-string str-key value-string) ] } @section{Transactions} @defform[(ks-transaction [ks keystore?] b1 ...)]{ Puts b1 ... in a "BEGIN/COMMIT" transaction. It uses with-handlers exn?, so if you raise an exception, it will do a "BEGIN/ROLLBACK" abd re-raise the exception. } @defproc[(ks-commit [ks keystore?]) boolean?]{ Commits (part of a transaction) using COMMIT/BEGIN. Fits in a @tt{ks-transaction} form. } @defproc[(ks-begin-transaction [ks keystore?]) boolean?]{ Begins a transaction with "BEGIN". } @defproc[(ks-end-transaction [ks keystore?]) boolean?]{ Commits a transaction with "COMMIT". } @defproc[(ks-abort-transaction [ks keystore?]) boolean?]{ Aborts a transaction with "ROLLBACK". } @section{Locking} @defproc[(ks-with-lock [ks keystore?] [proc (-> any/c)]) any/c]{ Calls @racket[proc] while holding the lock belonging to @racket[ks] and returns the value produced by @racket[proc]. Other threads using @racket[ks-with-lock] with the same keystore handle wait until the lock is released. The lock is reentrant: @racket[proc] may call @racket[ks-with-lock] again on the same handle and thread. The lock is released when control leaves @racket[proc], including when an exception is raised. The lock coordinates only code that uses the same keystore handle and explicitly calls @racket[ks-with-lock]. Individual keystore operations do not acquire it automatically. } @section{Examples} @subsection{Basic Usage} @racketblock[ (define ks (ks-open 'demo)) (ks-set! ks 'a 42) (ks-set! ks "b" '(1 2 3)) (ks-get ks 'a) ; => 42 (ks-get ks 'missing) ; => 'ks-nil (ks-get ks 'missing 0) ; => 0 ] @subsection{Enumeration Example} @racketblock[ (ks-keys ks) ;; => '(a "b") (ks-key-values ks) ;; => '((a . 42) ("b" . (1 2 3))) ] @subsection{Glob Query Example} @racketblock[ (ks-keys-glob ks "*b*") ] @subsection{Locking Example} @racketblock[ (ks-with-lock ks (λ () (ks-set! ks 'counter (add1 (ks-get ks 'counter 0))))) ]