Compare commits

..

10 Commits

Author SHA1 Message Date
b0e49ab6d1 fix scribble indcators 2026-04-18 11:32:45 +02:00
4ab6f539a1 Removed ambiguous characters 2026-04-17 22:49:33 +02:00
73a80ae3d6 Removed ambiguous characters 2026-04-17 22:48:41 +02:00
c24ce19ff9 Removed ambiguous characters 2026-04-17 22:47:59 +02:00
fee5a04ab8 info.rkt dependencies 2026-04-17 22:19:38 +02:00
c0a9d9fa25 info.rkt dependencies 2026-04-17 22:19:07 +02:00
31c51fbbc1 Created makefile for cleaning 2026-04-17 22:17:42 +02:00
b08e91c806 Removed scrbl created stuff 2026-04-17 22:17:29 +02:00
d6a7449d4a - 2026-04-17 22:13:56 +02:00
3c51066fad initial 2026-04-17 16:33:13 +02:00
8 changed files with 487 additions and 0 deletions

11
Makefile Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,7 @@
#lang racket/base
(require "keystore.rkt")
(provide (all-from-out "keystore.rkt"))

5
scrbl/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*.html
*.js
*.css
*~
*.bak

108
scrbl/class.scrbl Normal file
View 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
View 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*")
]