279 lines
9.3 KiB
Scheme
279 lines
9.3 KiB
Scheme
;; [[file:redis.org::*API][API:2]]
|
|
(import (scheme base)
|
|
(chicken base)
|
|
(chicken port)
|
|
(chicken string)
|
|
(chicken io)
|
|
(chicken tcp)
|
|
(srfi 34) ;; Exception Handling
|
|
(srfi 35) ;; Exception Types
|
|
(srfi 69) ;; Hash Tables
|
|
(srfi 113) ;; Sets and Bags
|
|
(srfi 128) ;; Comparators
|
|
(srfi 152) ;; Strings
|
|
(srfi 158) ;; Generators and Accumulators
|
|
)
|
|
;; API:2 ends here
|
|
|
|
;; Exceptions
|
|
;; This library defines an SRFI-35 exception type ~&redis-error~ that gets raised when Redis returns an error. The exception type has a single field called ~redis-error-message~ containing the error message returned by Redis.
|
|
|
|
;; [[file:redis.org::*Exceptions][Exceptions:1]]
|
|
(define-condition-type &redis-error &error
|
|
redis-error?
|
|
(redis-error-message redis-error-message))
|
|
;; Exceptions:1 ends here
|
|
|
|
|
|
|
|
;; Connects to a (hopefully) Redis server at =host:port=, using the given protocol version. Defaults, like Redis itself, to version 1.
|
|
|
|
|
|
;; [[file:redis.org::*Connection Management][Connection Management:2]]
|
|
(define-record-type redis-connection
|
|
(make-redis-connection input output) redis-connection?
|
|
(input redis-connection-input)
|
|
(output redis-connection-output))
|
|
(define (redis-connect host port #!optional (protocol-version 1))
|
|
(let-values (((i o) (tcp-connect host port)))
|
|
(values (make-redis-connection i o)
|
|
(and (write-line (string-append "HELLO " (->string protocol-version)) o)
|
|
(redis-read-reply i)))))
|
|
;; Connection Management:2 ends here
|
|
|
|
|
|
|
|
;; Disconnects from =rconn= which must be a =redis-connection=.
|
|
|
|
;; [[file:redis.org::*Connection Management][Connection Management:4]]
|
|
(define (redis-disconnect rconn)
|
|
(tcp-abandon-port (redis-connection-input rconn))
|
|
(tcp-abandon-port (redis-connection-output rconn)))
|
|
;; Connection Management:4 ends here
|
|
|
|
|
|
|
|
;; Uses connection =rconn= to run =command= with =args=. The args will be appended to the command, space-separated. Returns the parsed reply.
|
|
|
|
;; [[file:redis.org::*Running Commands][Running Commands:2]]
|
|
(define (redis-run rconn command #!rest args)
|
|
(let ((in (redis-connection-input rconn))
|
|
(out (redis-connection-output rconn))
|
|
(comm (string-join (cons command args))))
|
|
(write-line comm out)
|
|
(redis-read-reply in)))
|
|
;; Running Commands:2 ends here
|
|
|
|
|
|
|
|
;; Calls =proc= with the output port of the =rconn= as current output port, optionally with =args=. Returns the parsed reply.
|
|
|
|
;; [[file:redis.org::*Running Commands][Running Commands:4]]
|
|
(define (redis-run-proc rconn proc #!rest args)
|
|
(let ((in (redis-connection-input rconn))
|
|
(out (redis-connection-output rconn)))
|
|
(with-output-to-port out
|
|
(cut apply proc args))
|
|
(redis-read-reply in)))
|
|
;; Running Commands:4 ends here
|
|
|
|
;; Supported Data Types
|
|
|
|
;; This Redis client supports all data types up to and including as specified in [[https://github.com/antirez/RESP3/blob/master/spec.md][RESP3]].
|
|
|
|
;; #+name: redis-read-reply
|
|
|
|
;; [[file:redis.org::redis-read-reply][redis-read-reply]]
|
|
(define (redis-read-reply #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(sigil (read-char port)))
|
|
(case sigil
|
|
((#\+) (read-redis-simple-string port))
|
|
((#\-) (raise (make-condition &redis-error 'redis-error-message (read-redis-simple-string port))))
|
|
((#\$) (read-redis-blob-string port))
|
|
((#\!) (raise (make-condition &redis-error 'redis-error-message (read-redis-blob-string port))))
|
|
((#\=) (read-redis-blob-string port))
|
|
((#\:) (read-redis-number port))
|
|
((#\,) (read-redis-number port))
|
|
((#\() (read-redis-number port))
|
|
((#\#) (read-redis-bool port))
|
|
((#\_) (read-redis-null port))
|
|
((#\*) (read-redis-array port))
|
|
((#\%) (read-redis-map port))
|
|
((#\~) (read-redis-set port))
|
|
((#\|) (read-redis-with-attributes port)))))
|
|
;; redis-read-reply ends here
|
|
|
|
;; Simple Strings
|
|
;; Simple strings start with ~+~ and are single-line.
|
|
|
|
;; #+name: read-redis-simple-string-example
|
|
;; #+begin_example
|
|
;; +this is a simple string.
|
|
;; #+end_example
|
|
|
|
;; #+name: read-redis-simple-string
|
|
|
|
;; [[file:redis.org::read-redis-simple-string][read-redis-simple-string]]
|
|
(define (read-redis-simple-string #!optional port)
|
|
(let ((port (or port (current-input-port))))
|
|
(read-line port)))
|
|
;; read-redis-simple-string ends here
|
|
|
|
;; Blob Strings
|
|
;; Blob strings are longer, potentially multi-line strings. Their sigil is ~$~, followed by an integer designating the string length.
|
|
|
|
;; #+begin_example
|
|
;; $7
|
|
;; chicken
|
|
;; #+end_example
|
|
|
|
;; #+name: read-redis-blob-string
|
|
|
|
;; [[file:redis.org::read-redis-blob-string][read-redis-blob-string]]
|
|
(define (read-redis-blob-string #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(charcount (string->number (read-line port)))
|
|
(str (list->string
|
|
(generator-map->list
|
|
(lambda (i) (read-char port))
|
|
(make-range-generator 0 charcount)))))
|
|
(read-line port)
|
|
str))
|
|
;; read-redis-blob-string ends here
|
|
|
|
;; Integers
|
|
;; Integers are sent to the client prefixed with ~:~.
|
|
|
|
;; #+begin_example
|
|
;; :180
|
|
;; #+end_example
|
|
|
|
;; #+name: read-redis-number
|
|
|
|
;; [[file:redis.org::read-redis-number][read-redis-number]]
|
|
(define (read-redis-number #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(elem (read-line port)))
|
|
(if (string=? elem "inf")
|
|
(string->number "+inf")
|
|
(string->number elem))))
|
|
;; read-redis-number ends here
|
|
|
|
;; Booleans
|
|
;; True and false values are represented as ~#t~ and ~#f~, just like in Scheme.
|
|
|
|
;; #+name: read-redis-bool
|
|
|
|
;; [[file:redis.org::read-redis-bool][read-redis-bool]]
|
|
(define (read-redis-bool #!optional port)
|
|
(let ((port (or port (current-input-port))))
|
|
(string=? (read-line port) "t")))
|
|
;; read-redis-bool ends here
|
|
|
|
;; Null
|
|
;; The null type is encoded simply as ~_~, and results in ~'()~.
|
|
|
|
;; #+name: read-redis-null
|
|
|
|
;; [[file:redis.org::read-redis-null][read-redis-null]]
|
|
(define (read-redis-null #!optional port)
|
|
(let ((port (or port (current-input-port))))
|
|
(read-line port) '()))
|
|
;; read-redis-null ends here
|
|
|
|
;; Arrays
|
|
;; Arrays are marked with ~*~ followed by the number of entries, and get returned as srfi-133 vectors.
|
|
|
|
;; #+begin_example
|
|
;; *3
|
|
;; :1
|
|
;; :2
|
|
;; :3
|
|
;; #+end_example
|
|
|
|
;; #+name: read-redis-array
|
|
|
|
;; [[file:redis.org::read-redis-array][read-redis-array]]
|
|
(define (read-redis-array #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(elems (string->number (read-line port)))
|
|
(vec (make-vector elems)))
|
|
(generator-for-each
|
|
(lambda (i)
|
|
(vector-set! vec i (redis-read-reply port)))
|
|
(make-range-generator 0 elems))
|
|
vec))
|
|
;; read-redis-array ends here
|
|
|
|
;; Maps
|
|
;; Maps are represented exactly as arrays, but instead of using the ~*~ byte, the encoded value starts with a ~%~ byte. Moreover the number of following elements must be even. Maps represent a sequence of field-value items, basically what we could call a dictionary data structure, or in other terms, an hash. They get returned as srfi-69 hash tables.
|
|
|
|
;; #+begin_example
|
|
;; %2
|
|
;; +first
|
|
;; :1
|
|
;; +second
|
|
;; :2
|
|
;; #+end_example
|
|
|
|
;; #+name: read-redis-map
|
|
|
|
;; [[file:redis.org::read-redis-map][read-redis-map]]
|
|
(define (read-redis-map #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(elems (string->number (read-line port)))
|
|
(ht (make-hash-table)))
|
|
(generator-for-each
|
|
(lambda (i)
|
|
(hash-table-set! ht (redis-read-reply port) (redis-read-reply port)))
|
|
(make-range-generator 0 elems))
|
|
ht))
|
|
;; read-redis-map ends here
|
|
|
|
;; Sets
|
|
;; Sets are exactly like the Array type, but the first byte is ~~~ instead of ~*~. They get returned as srfi-113 sets.
|
|
;; Additionally, there is a parameter defined, =redis-set-comparator=, that specifies the default comparator to be used for sets. It defaults to `(make-default-comparator)`.
|
|
|
|
;; #+begin_example
|
|
;; ~4
|
|
;; +orange
|
|
;; +apple
|
|
;; #t
|
|
;; #f
|
|
;; #+end_example
|
|
|
|
;; #+name: read-redis-set
|
|
|
|
;; [[file:redis.org::read-redis-set][read-redis-set]]
|
|
(define redis-set-comparator
|
|
(make-parameter (make-default-comparator)
|
|
(lambda (newcomp)
|
|
(or (and (comparator? newcomp)
|
|
newcomp)
|
|
'()))))
|
|
|
|
(define (read-redis-set #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(elems (string->number (read-line port)))
|
|
(s (set (redis-set-comparator))))
|
|
(generator-for-each
|
|
(lambda (i)
|
|
(set-adjoin! s (redis-read-reply port)))
|
|
(make-range-generator 0 elems))
|
|
s))
|
|
;; read-redis-set ends here
|
|
|
|
;; Attributes
|
|
;; The attribute type is exactly like the Map type, but instead of the ~%~ first byte, the ~|~ byte is used. Attributes describe a dictionary exactly like the Map type, however the client should not consider such a dictionary part of the reply, but just auxiliary data that is used in order to augment the reply.
|
|
|
|
;; This library returns two values in this case, the first value being the actual data reply from redis, the second one being the attributes.
|
|
|
|
;; #+name: read-redis-with-attributes
|
|
|
|
;; [[file:redis.org::read-redis-with-attributes][read-redis-with-attributes]]
|
|
(define (read-redis-with-attributes #!optional port)
|
|
(let* ((port (or port (current-input-port)))
|
|
(attributes (read-redis-map port)))
|
|
(values (redis-read-reply port) attributes)))
|
|
;; read-redis-with-attributes ends here
|