r

redin

v0.1.0library

No description provided.

0 stars0 forksMITupdated 5 months ago
Open repo

redin

A pure-Odin Redis client with no external dependencies.

Language Protocol License


About

redin is a lightweight Redis client for the Odin programming language, implementing the RESP2 (REdis Serialization Protocol v2) wire protocol from scratch over a plain TCP socket. It covers all major Redis data structures and includes support for pipelining.

The initial codebase was entirely generated by Claude (Anthropic) and subsequently reviewed, tested and refined by the author.

Features

  • Zero external dependencies — only Odin's core library
  • Full RESP2 parser (Simple String, Error, Integer, Bulk String, Array, Null)
  • High-level API for Strings, Lists, Hashes, Sets, Sorted Sets
  • Pipelining support (multiple commands in a single round-trip)
  • Raw exec escape hatch for any command not covered by the API
  • Optional password auth and database selection

Project Structure

redin/
├── redis.odin          # client library
└── example/
    └── main.odin       # usage examples

Getting Started

Make sure Redis is running locally, then:

# Build the example
odin build example/ -out:redis_example

# Run it (requires a Redis instance on 127.0.0.1:6379)
./redis_example

API Reference

Connection

c, err := redis.connect("127.0.0.1", 6379)
if err != "" { /* handle */ }
defer redis.close(&c)

redis.auth(&c, "password")   // optional authentication
redis.select_db(&c, 1)       // select database (default: 0)

Strings

redis.set(&c, "key", "value")
redis.set_ex(&c, "token", "abc", 3600)   // expires in 1h
redis.set_nx(&c, "lock",  "1")           // set only if not exists

val, found, err := redis.get(&c, "key")

redis.incr(&c, "counter")
redis.incrby(&c, "counter", 5)
redis.decr(&c, "counter")
redis.decrby(&c, "counter", 3)

redis.mset(&c, "k1", "v1", "k2", "v2")
values, _ := redis.mget(&c, "k1", "k2", "missing")

redis.append_str(&c, "log", " entry")
redis.strlen(&c, "key")

Lists

redis.rpush(&c, "list", "a", "b", "c")
redis.lpush(&c, "list", "z")

items, _ := redis.lrange(&c, "list", 0, -1)
redis.llen(&c, "list")
redis.lpop(&c, "list")
redis.rpop(&c, "list")
redis.lindex(&c, "list", 0)
redis.lset(&c, "list", 0, "new")
redis.lrem(&c, "list", 1, "a")
redis.ltrim(&c, "list", 0, 9)

Hashes

redis.hset(&c, "user:1", "name", "Alice")
redis.hmset(&c, "user:1", "name", "Alice", "age", "30", "city", "Rome")

val, found, _ := redis.hget(&c, "user:1", "name")
m, _          := redis.hgetall(&c, "user:1")   // → map[string]string

redis.hdel(&c, "user:1", "city")
redis.hexists(&c, "user:1", "name")
redis.hincrby(&c, "user:1", "age", 1)
redis.hkeys(&c, "user:1")
redis.hvals(&c, "user:1")
redis.hlen(&c, "user:1")

Sets

redis.sadd(&c, "tags", "odin", "redis", "programming")
redis.srem(&c, "tags", "programming")
redis.smembers(&c, "tags")
redis.sismember(&c, "tags", "odin")   // → bool
redis.scard(&c, "tags")

redis.sunion(&c, "tags", "more_tags")
redis.sinter(&c, "tags", "other_tags")
redis.sdiff(&c,  "tags", "other_tags")

Sorted Sets

redis.zadd(&c, "leaderboard", 100.0, "alice")
redis.zadd(&c, "leaderboard",  85.5, "bob")

redis.zrange(&c, "leaderboard", 0, -1)           // members only
redis.zrange(&c, "leaderboard", 0, -1, true)     // with scores

rank,  found, _ := redis.zrank(&c,  "leaderboard", "alice")
score, found, _ := redis.zscore(&c, "leaderboard", "alice")

redis.zrem(&c, "leaderboard", "bob")
redis.zcard(&c, "leaderboard")

Key Management

redis.del(&c, "k1", "k2", "k3")
redis.exists(&c, "k1")
redis.expire(&c,  "k1", 60)     // seconds
redis.pexpire(&c, "k1", 5000)   // milliseconds
redis.ttl(&c,   "k1")
redis.pttl(&c,  "k1")
redis.persist(&c, "k1")         // remove expiry
redis.keys(&c, "prefix:*")
redis.rename(&c, "old", "new")
redis.key_type(&c, "k1")        // "string" | "list" | "hash" | "set" | "zset"

Server Commands

redis.ping(&c)
redis.dbsize(&c)
redis.flushdb(&c)
redis.flushall(&c)

Pipelining

Send multiple commands in a single round-trip for maximum throughput:

pipe := redis.pipeline_begin(&c)
redis.pipeline_queue(&pipe, "SET", "k1", "v1")
redis.pipeline_queue(&pipe, "SET", "k2", "v2")
redis.pipeline_queue(&pipe, "GET", "k1")
redis.pipeline_queue(&pipe, "GET", "k2")

results, err := redis.pipeline_exec(&pipe)
for r in results {
    redis.value_print(r)
}

Raw Commands

For any command not covered by the high-level API:

v, err := redis.exec(&c, "COMMAND", "COUNT")
v, err  = redis.exec(&c, "CONFIG", "GET", "maxmemory")
redis.value_print(v)

The Value Type

All responses are parsed into a Value union:

Value_Kind :: enum {
    Simple_String,
    Error,
    Integer,
    Bulk_String,
    Array,
    Null,
}

Value :: struct {
    kind:    Value_Kind,
    str:     string,    // Simple_String | Bulk_String | Error
    integer: i64,       // Integer
    array:   []Value,   // Array
}

Use redis.value_destroy(&v) to recursively free a Value and all its children.


Memory Notes

  • Strings returned by high-level procedures are allocated with strings.clone — free them when done, or use an arena allocator for the whole request.
  • Use redis.value_destroy(&v) to recursively free raw Value responses.
  • Client.buf is a dynamic buffer allocated on connect and freed by close.

License

MIT — see LICENSE for details.


Credits

Initial code fully generated by Claude (Anthropic) · reviewed and refined by the author.

Package Info
Version
v0.1.0
License
MIT
Author
@o3o
Type
library
Forks
0
Created
5 months ago
Updated
5 months ago
Health
maintained
has releases
has readme
has license
Maintainer
o3o
o3o
Maintainer
Activity
last 56 weeks