Compare commits
10 Commits
61e3e255ea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b0e49ab6d1 | |||
| 4ab6f539a1 | |||
| 73a80ae3d6 | |||
| c24ce19ff9 | |||
| fee5a04ab8 | |||
| c0a9d9fa25 | |||
| 31c51fbbc1 | |||
| b08e91c806 | |||
| d6a7449d4a | |||
| 3c51066fad |
11
Makefile
Normal file
11
Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
all:
|
||||
@echo use make clean
|
||||
|
||||
clean:
|
||||
find . -type f -name "*~" -exec rm {} \;
|
||||
find . -type f -name "*.bak" -exec rm {} \;
|
||||
find scrbl -type f -name "*.html" -exec rm {} \;
|
||||
find scrbl -type f -name "*.js" -exec rm {} \;
|
||||
find scrbl -type f -name "*.css" -exec rm {} \;
|
||||
54
class.rkt
Normal file
54
class.rkt
Normal file
@@ -0,0 +1,54 @@
|
||||
#lang racket/base
|
||||
|
||||
(require "keystore.rkt"
|
||||
racket/class
|
||||
)
|
||||
|
||||
(provide keystore%)
|
||||
|
||||
|
||||
(define keystore%
|
||||
(class object%
|
||||
(init-field
|
||||
[file (error "provide a filename using a path, string or use a symbol")]
|
||||
)
|
||||
|
||||
(super-new)
|
||||
|
||||
(define ksh (ks-open file))
|
||||
|
||||
(define/public (handle)
|
||||
ksh)
|
||||
|
||||
(define/public (set! key val)
|
||||
(ks-set! ksh key val)
|
||||
this)
|
||||
|
||||
(define/public (get key . default-val)
|
||||
(if (null? default-val)
|
||||
(ks-get ksh key)
|
||||
(ks-get ksh key (car default-val))))
|
||||
|
||||
(define/public (drop! key)
|
||||
(ks-drop! ksh key)
|
||||
this)
|
||||
|
||||
(define/public (exists? key)
|
||||
(ks-exists? ksh key))
|
||||
|
||||
(define/public (glob gl)
|
||||
(ks-keys-glob ksh gl))
|
||||
|
||||
(define/public (glob-kv gl)
|
||||
(ks-key-values-glob ksh gl))
|
||||
|
||||
(define/public (keys-glob gl)
|
||||
(ks-keys-glob ksh gl))
|
||||
|
||||
(define/public (keys)
|
||||
(ks-keys ksh))
|
||||
|
||||
(define/public (key-values)
|
||||
(ks-key-values ksh))
|
||||
)
|
||||
)
|
||||
28
info.rkt
Normal file
28
info.rkt
Normal file
@@ -0,0 +1,28 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.1.1")
|
||||
(define license 'MIT)
|
||||
(define collection "keystore")
|
||||
(define pkg-desc "keystore - A keystore for keys/values using the racket db/sqlite module as basis")
|
||||
|
||||
(define scribblings
|
||||
'(
|
||||
("scrbl/keystore.scrbl" () (library))
|
||||
("scrbl/class.scrbl" () (library))
|
||||
)
|
||||
)
|
||||
|
||||
(define deps
|
||||
'("racket/base"
|
||||
"db"
|
||||
)
|
||||
)
|
||||
|
||||
(define build-deps
|
||||
'("racket-doc"
|
||||
"draw-doc"
|
||||
"rackunit-lib"
|
||||
"scribble-lib"
|
||||
))
|
||||
|
||||
135
keystore.rkt
Normal file
135
keystore.rkt
Normal file
@@ -0,0 +1,135 @@
|
||||
#lang racket/base
|
||||
|
||||
(require racket/contract
|
||||
racket/serialize
|
||||
racket/port
|
||||
db
|
||||
)
|
||||
|
||||
(provide ks-open
|
||||
ks-set!
|
||||
ks-get
|
||||
ks-drop!
|
||||
ks-exists?
|
||||
ks-keys
|
||||
ks-keys-glob
|
||||
ks-key-values-glob
|
||||
ks-key-values
|
||||
ks-key-values-raw
|
||||
ks-keys-raw
|
||||
)
|
||||
|
||||
(define-struct keystore
|
||||
(file path dbh)
|
||||
#:transparent
|
||||
)
|
||||
|
||||
(define/contract (ks-open file)
|
||||
(-> (or/c path? string? symbol?) keystore?)
|
||||
(let ((path (if (symbol? file)
|
||||
(build-path (find-system-path 'cache-dir) (format "ks-~a.db" file))
|
||||
(build-path file))))
|
||||
(let ((dbh (sqlite3-connect #:database path #:mode 'create)))
|
||||
(query-exec dbh "CREATE TABLE IF NOT EXISTS keystore(key varchar primary key, value varchar, str_key varchar);")
|
||||
(query-exec dbh "CREATE INDEX IF NOT EXISTS keystore_idx on keystore(str_key);")
|
||||
(make-keystore file path dbh))))
|
||||
|
||||
(define/contract (ks-set! ksh key value)
|
||||
(-> keystore? any/c any/c boolean?)
|
||||
(ks-set!* (keystore-dbh ksh) (value->string key) (format "~a" key) (value->string value)))
|
||||
|
||||
(define/contract (ks-exists? ksh key)
|
||||
(-> keystore? any/c boolean?)
|
||||
(ks-exists?* (keystore-dbh ksh) (value->string key)))
|
||||
|
||||
(define/contract (ks-get ksh key . default-value)
|
||||
(-> keystore? any/c ... any/c any/c)
|
||||
(ks-get* (keystore-dbh ksh) (value->string key) default-value))
|
||||
|
||||
(define/contract (ks-drop! ksh key)
|
||||
(-> keystore? any/c boolean?)
|
||||
(ks-drop!* (keystore-dbh ksh) (value->string key)))
|
||||
|
||||
(define/contract (ks-key-values-glob ksh gl)
|
||||
(-> keystore? string? list?)
|
||||
(map (λ (row)
|
||||
(cons (string->value (vector-ref row 0)) (string->value (vector-ref row 1))))
|
||||
(query-rows (keystore-dbh ksh)
|
||||
"SELECT key, value FROM keystore WHERE str_key GLOB $1"
|
||||
(string-downcase gl))))
|
||||
|
||||
(define/contract (ks-keys-glob ksh sqlite-like)
|
||||
(-> keystore? string? list?)
|
||||
(map (λ (row)
|
||||
(string->value (vector-ref row 0)))
|
||||
(query-rows (keystore-dbh ksh)
|
||||
"SELECT key FROM keystore WHERE str_key GLOB $1"
|
||||
(string-downcase sqlite-like))))
|
||||
|
||||
(define/contract (ks-keys ksh)
|
||||
(-> keystore? list?)
|
||||
(ks-keys* (keystore-dbh ksh)))
|
||||
|
||||
(define/contract (ks-key-values ksh)
|
||||
(-> keystore? list?)
|
||||
(ks-key-values* (keystore-dbh ksh)))
|
||||
|
||||
(define/contract (ks-keys-raw ksh)
|
||||
(-> keystore? list?)
|
||||
(map vector->list
|
||||
(query-rows (keystore-dbh ksh) "SELECT key, str_key FROM keystore")))
|
||||
|
||||
(define/contract (ks-key-values-raw ksh)
|
||||
(-> keystore? list?)
|
||||
(map vector->list
|
||||
(query-rows (keystore-dbh ksh) "SELECT key, str_key, value FROM keystore")))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Internal working
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(define (ks-set!* dbh key str-key value)
|
||||
(query-exec dbh "INSERT OR REPLACE INTO keystore VALUES($1, $2, $3)"
|
||||
key value (string-downcase str-key))
|
||||
#t)
|
||||
|
||||
(define (ks-exists?* dbh key)
|
||||
(> (query-value dbh "SELECT COUNT(*) FROM keystore WHERE key = $1" key) 0))
|
||||
|
||||
(define (ks-get* dbh key default-value)
|
||||
(if (ks-exists?* dbh key)
|
||||
(string->value (query-value dbh "SELECT value FROM keystore WHERE key = $1" key))
|
||||
(if (null? default-value)
|
||||
'ks-nil
|
||||
(car default-value))
|
||||
)
|
||||
)
|
||||
|
||||
(define (ks-drop!* dbh key)
|
||||
(query-exec dbh "DELETE FROM keystore WHERE key = $1" key)
|
||||
#t)
|
||||
|
||||
(define (ks-keys* dbh)
|
||||
(map (λ (row)
|
||||
(string->value (vector-ref row 0)))
|
||||
(query-rows dbh "SELECT key FROM keystore")))
|
||||
|
||||
(define (ks-key-values* dbh)
|
||||
(map (λ (row)
|
||||
(cons (string->value (vector-ref row 0))
|
||||
(string->value (vector-ref row 1))))
|
||||
(query-rows dbh "SELECT key, value FROM keystore")))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; serialization
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define (value->string v)
|
||||
(call-with-output-string
|
||||
(λ (out) (write (serialize v) out))))
|
||||
|
||||
(define (string->value v)
|
||||
(deserialize
|
||||
(call-with-input-string v (λ (in) (read in)))))
|
||||
|
||||
7
main.rkt
Normal file
7
main.rkt
Normal file
@@ -0,0 +1,7 @@
|
||||
#lang racket/base
|
||||
|
||||
|
||||
(require "keystore.rkt")
|
||||
|
||||
(provide (all-from-out "keystore.rkt"))
|
||||
|
||||
5
scrbl/.gitignore
vendored
Normal file
5
scrbl/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
*.html
|
||||
*.js
|
||||
*.css
|
||||
*~
|
||||
*.bak
|
||||
108
scrbl/class.scrbl
Normal file
108
scrbl/class.scrbl
Normal file
@@ -0,0 +1,108 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base
|
||||
racket/class
|
||||
"../keystore.rkt"
|
||||
"../class.rkt"))
|
||||
|
||||
@title{Keystore Class}
|
||||
|
||||
@defmodule[keystore/class]
|
||||
|
||||
An object-oriented wrapper around @racketmodname[keystore]. It provides
|
||||
a small class interface to the persistent key-value store.
|
||||
|
||||
@defclass[keystore% object% ()]{
|
||||
|
||||
A keystore class backed by SQLite.
|
||||
|
||||
@defconstructor[([file (or/c path? string? symbol?)])]{
|
||||
|
||||
Opens or creates a keystore using @racket[file]. The meaning of
|
||||
@racket[file] is the same as for @racket[ks-open].
|
||||
}
|
||||
|
||||
@defmethod[(handle) keystore?]{
|
||||
|
||||
Returns the underlying keystore handle.
|
||||
}
|
||||
|
||||
@defmethod[(set! [key any/c] [value any/c]) (is-a?/c keystore%)]{
|
||||
|
||||
Stores @racket[value] under @racket[key]. Returns the object itself,
|
||||
so method calls may be nested.
|
||||
}
|
||||
|
||||
@defmethod[(get [key any/c] [default any/c] ...) any/c]{
|
||||
|
||||
Retrieves the value associated with @racket[key]. If no value is found,
|
||||
the optional default is returned; otherwise @racket['ks-nil] is returned.
|
||||
}
|
||||
|
||||
@defmethod[(drop! [key any/c]) (is-a?/c keystore%)]{
|
||||
|
||||
Removes @racket[key] from the store and returns the object itself.
|
||||
}
|
||||
|
||||
@defmethod[(exists? [key any/c]) boolean?]{
|
||||
|
||||
Returns @racket[#t] if @racket[key] exists, and @racket[#f] otherwise.
|
||||
}
|
||||
|
||||
@defmethod[(glob [pattern string?]) (listof any/c)]{
|
||||
|
||||
Returns keys matching @racket[pattern].
|
||||
}
|
||||
|
||||
@defmethod[(glob-kv [pattern string?]) (listof (cons/c any/c any/c))]{
|
||||
|
||||
Returns key=value pairs matching @racket[pattern].
|
||||
}
|
||||
|
||||
@defmethod[(keys-glob [pattern string?]) (listof any/c)]{
|
||||
|
||||
Alias for @racket[glob].
|
||||
}
|
||||
|
||||
@defmethod[(keys) (listof any/c)]{
|
||||
|
||||
Returns all keys.
|
||||
}
|
||||
|
||||
@defmethod[(key-values) (listof (cons/c any/c any/c))]{
|
||||
|
||||
Returns all key-value pairs.
|
||||
}
|
||||
}
|
||||
|
||||
@section{Examples}
|
||||
|
||||
@subsection{Basic Usage}
|
||||
|
||||
@racketblock[
|
||||
(define ks (new keystore% [file 'demo]))
|
||||
|
||||
(send ks set! 'a 42)
|
||||
(send ks set! "b" '(1 2 3))
|
||||
|
||||
(send ks get 'a) ; => 42
|
||||
(send ks get 'missing) ; => 'ks-nil
|
||||
(send ks get 'missing 0) ; => 0
|
||||
]
|
||||
|
||||
@subsection{Nested Calls}
|
||||
|
||||
@racketblock[
|
||||
(define ks (new keystore% [file 'demo]))
|
||||
|
||||
(send (send (send ks set! 'a 1)
|
||||
set! 'b 2)
|
||||
drop! 'a)
|
||||
]
|
||||
|
||||
@subsection{Glob Queries}
|
||||
|
||||
@racketblock[
|
||||
(send ks glob "*b*")
|
||||
(send ks glob-kv "*b*")
|
||||
]
|
||||
139
scrbl/keystore.scrbl
Normal file
139
scrbl/keystore.scrbl
Normal file
@@ -0,0 +1,139 @@
|
||||
#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])]{
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
@section{Opening}
|
||||
|
||||
@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.
|
||||
}
|
||||
|
||||
@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-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{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*")
|
||||
]
|
||||
Reference in New Issue
Block a user