Files
keystore/scrbl/keystore.scrbl
T

219 lines
5.4 KiB
Racket

#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.
}
@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)))))
]