Hiredis

A Redis client library for CHICKEN Scheme that provides a simple interface to interact with Redis servers using the hiredis C library.

Description

Hiredis is a lightweight Redis client wrapper that allows you to execute Redis commands from CHICKEN Scheme with automatic reply parsing. It provides a simple Scheme-friendly API for interacting with Redis servers, including support for pub/sub functionality. The library converts Redis replies to appropriate Scheme data types and handles connection management transparently.

Requirements

Installation

First, ensure you have the hiredis C library installed on your system:

# On macOS with Homebrew
brew install hiredis

# On Ubuntu/Debian
sudo apt-get install libhiredis-dev

# On CentOS/RHEL
sudo yum install hiredis-devel

Then install the CHICKEN egg:

chicken-install hiredis

Basic Usage

(import hiredis)

;; Connect to Redis server & set context for current thread
(redis-context (redis-connect "127.0.0.1" 6379))

;; Execute Redis commands
(redis-command "SET" "mykey" "myvalue")
(redis-command "GET" "mykey")
(redis-command "HSET" "myhash" "field1" "value1")
(redis-command "HGET" "myhash" "field1")
(redis-command "KEYS" "*")

API

[procedure] (redis-connect [hostname] [port])

Connect to a Redis server.

Parameters:

Returns: Redis connection context pointer that can be used to set the redis-context parameter.

(redis-connect "127.0.0.1" 6379)
(redis-connect "localhost" 6379)
(redis-connect)  ; Uses defaults: localhost:6379
(redis-connect "redis.example.com")  ; Uses default port 6379

After connecting you will need to set the context for all the redis functions. This ensures that your local thread has a context available.

(redis-context (redis-connect ...))
[parameter] (redis-context)

Parameter that holds the current Redis connection context. Must be set after connecting before using other Redis functions.

[procedure] (redis-command command . args)

Execute a Redis command with optional arguments.

Parameters:

Returns: Scheme object representation of Redis reply

;; String operations
(redis-command "GET" "mykey")
(redis-command "SET" "mykey" "myvalue")

;; Hash operations
(redis-command "HGET" "myhash" "field")
(redis-command "HSET" "myhash" "field" "value")

;; List operations
(redis-command "LPUSH" "mylist" "item1" "item2")
(redis-command "LRANGE" "mylist" "0" "-1")

;; Key operations
(redis-command "KEYS" "*")
(redis-command "EXISTS" "mykey")
(redis-command "DEL" "key1" "key2")
[procedure] (redis-subscribe channel callback)

Subscribe to a Redis channel using pattern matching (internally uses the `PSUBSCRIBE` command).

Parameters:

Callback Function: The callback receives a list with 4 elements for pattern messages: `("pmessage" "pattern" "channel" "message")`

The callback should return non-false to continue listening, or #f to unsubscribe.

;; Subscribe to all channels starting with "test"
(redis-subscribe "test*" 
  (lambda (reply)
    (let ((msg (list-ref reply 3)))
      (format #t "Received: ~A\n" msg)
      (not (string=? msg "quit")))))

;; Subscribe to a specific channel
(redis-subscribe "notifications"
  (lambda (reply)
    (let ((channel (list-ref reply 2))
          (message (list-ref reply 3)))
      (format #t "Channel ~A: ~A\n" channel message)
      #t))) ; Continue listening indefinitely

Reply Types

The library automatically converts Redis replies to appropriate Scheme objects:

;; String reply
(redis-command "GET" "mykey") ; => "myvalue"

;; Integer reply
(redis-command "INCR" "counter") ; => 1

;; Array reply
(redis-command "LRANGE" "mylist" "0" "-1") ; => ("item1" "item2" "item3")

;; Nil reply
(redis-command "GET" "nonexistent") ; => #f

;; Status reply
(redis-command "SET" "key" "value") ; => "OK"

;; Error reply
(redis-command "INVALID" "command") ; => (error . "ERR unknown command")

;; Double reply (Redis 6.2+)
(redis-command "HINCRBYFLOAT" "hash" "field" "1.5") ; => 1.5

Complete Examples

Basic Redis Operations

(import hiredis)

;; Connect and set context
(define ctx (redis-connect "localhost" 6379))
(redis-context ctx)

;; String operations
(redis-command "SET" "user:1000:name" "John Doe")
(redis-command "GET" "user:1000:name") ; => "John Doe"

;; Increment operations
(redis-command "SET" "page:views" "10")
(redis-command "INCR" "page:views") ; => 11
(redis-command "INCRBY" "page:views" "5") ; => 16

;; Hash operations
(redis-command "HSET" "user:1000" "name" "John" "email" "john@example.com")
(redis-command "HGET" "user:1000" "name") ; => "John"
(redis-command "HGETALL" "user:1000") ; => ("name" "John" "email" "john@example.com")

;; List operations
(redis-command "LPUSH" "tasks" "task1" "task2" "task3")
(redis-command "LRANGE" "tasks" "0" "-1") ; => ("task3" "task2" "task1")
(redis-command "RPOP" "tasks") ; => "task1"

Pub/Sub Example

;; Publisher (in one process/thread)
(define pub-ctx (redis-connect))
(redis-context pub-ctx)

(redis-command "PUBLISH" "notifications" "Hello World!")
(redis-command "PUBLISH" "test-channel" "Test message")

;; Subscriber (in another process/thread)
(define sub-ctx (redis-connect))
(redis-context sub-ctx)

;; Subscribe to all channels matching pattern
(redis-subscribe "test*"
  (lambda (reply)
    (let ((type (list-ref reply 0))
          (pattern (list-ref reply 1))
          (channel (list-ref reply 2))
          (message (list-ref reply 3)))
      (format #t "Type: ~A, Pattern: ~A, Channel: ~A, Message: ~A\n" 
              type pattern channel message)
      ;; Continue listening unless message is "quit"
      (not (string=? message "quit")))))

Error Handling

(define (safe-redis-command command . args)
  (let ((result (apply redis-command command args)))
    (if (and (pair? result) (eq? (car result) 'error))
        (begin
          (format #t "Redis error: ~A\n" (cdr result))
          #f)
        result)))

;; Usage
(safe-redis-command "GET" "mykey")
(safe-redis-command "INVALID" "command") ; Prints error, returns #f

Files

License

Copyright © 2025 Rolando Abarca. Licensed under the BSD 3-Clause License.

Repository

GitHub Repository