db/redis
A Redis client: keys and values, hashes, lists, sets and publishing.
import db.redis
- Tutorial: a link shortener in twelve steps
- 1. Connect and log in
- 2. Strings and missing keys
- 3. Records in hashes
- 4. Counters
- 5. Lists
- 6. Sets
- 7. Expiry
- 8. Any command: a leaderboard
- 9. Transactions
- 10. Errors
- 11. Several threads
- 12. Look around and clean up
- More
- Guide: cache a slow page
- Guide: limit requests per user
- Guide: a job queue with a worker
- Which function for which job
- Types
- Constants and variables
- Functions
Redis is an in memory database for caches, sessions, counters, queues and leaderboards. This package speaks the Redis protocol directly over a socket, with no outside library: one function per common command for strings, hashes, lists and sets, plus Command, which sends any other command with its arguments and hands back the raw reply. This page starts with a tutorial; the full reference of every function and type comes after it.
Tutorial: a link shortener in twelve steps
The steps below build the storage of a small link shortener: link records, click counters, a list of recent links, unique visitors and a leaderboard. Each step is a complete program, and the output under it is what it printed against a real Redis 7.4. Run them in order, because each step uses the keys the earlier ones left behind. The last step deletes everything again.
First start a server with a password. With Docker that is one command:
docker run -p 6379:6379 redis:7.4 redis-server --requirepass s3cret
The programs connect to 127.0.0.1:6379 and log in with s3cret. To start over at any point, run docker exec on the container with redis-cli -a s3cret flushall, then begin again at step 1.
1. Connect and log in
Connect opens one TCP connection and returns a Client. Ok tells you whether that worked and Error says why not. A server with a password refuses every command until Auth succeeds, which is why the first Ping is false. Select switches to another numbered database; a default server has 16, numbered 0 to 15.
import db.redis
func main:
c := redis.Connect("127.0.0.1", 6379)
if not redis.Ok(c):
println "cannot reach Redis:", redis.Error(c)
ret 1
end
defer redis.Close(c)
println "ping before login:", redis.Ping(c)
println "wrong password:", redis.Auth(c, "guess")
println "right password:", redis.Auth(c, "s3cret")
println "ping after login:", redis.Ping(c)
println "database 15:", redis.Select(c, 15)
println "database 99:", redis.Select(c, 99)
println "back to 0:", redis.Select(c, 0)
off := redis.Connect("127.0.0.1", 6390)
println "port 6390:", redis.Ok(off), redis.Error(off)
endEvery function in this package returns a plain value (a bool, a number, a string or a vector) instead of an error object. When something goes wrong you get the empty value: false, 0 or an empty string. Step 10 shows how to tell the cases apart when that matters. Always Close a client: it closes the socket and frees the buffer.
2. Strings and missing keys
The simplest Redis value is a string under a key. Set stores it, Get reads it back and Append adds to the end. Redis has no NULL: a key is either there or not. Get returns an empty string in both cases, so when an empty value is meaningful, ask Exists, or send GET through Command and test the reply with ReplyIsNil.
import db.redis
import str
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
redis.Set(c, "site:name", "Short links")
redis.Set(c, "site:motto", "")
println "name:", redis.Get(c, "site:name")
println "motto: [" + redis.Get(c, "site:motto") + "]", redis.Exists(c, "site:motto")
println "owner: [" + redis.Get(c, "site:owner") + "]", redis.Exists(c, "site:owner")
mut args := Vector {} as Vector<str>
args.push("GET")
args.push("site:owner")
r := redis.Command(c, args)
println "GET site:owner is nil:", redis.ReplyIsNil(r)
redis.FreeReply(r)
tricky := "x\r\nFLUSHALL\r\n"
redis.Set(c, "site:tricky", tricky)
println "stored bytes:", redis.StrLen(c, "site:tricky"), "of", tricky.len()
println "same text back:", str.Equals(redis.Get(c, "site:tricky"), tricky)
println "name still there:", redis.Exists(c, "site:name")
redis.Del(c, "site:tricky")
println "length after append:", redis.Append(c, "site:name", " for Salam")
println "name:", redis.Get(c, "site:name")
endThere is no injection to worry about. Each argument travels as a length followed by its bytes, so a value with spaces, quotes or line breaks is stored exactly as it is and can never be read as a second command. The one limit is the NUL byte: Salam strings end at the first one, so keep binary data out of these functions or encode it first, with Base64EncodeBytes from encoding for instance.
3. Records in hashes
A hash holds named fields under one key, which is the natural place for a record. Incr on a counter key hands out ids that never repeat, even with many programs adding links at once. HSet writes one field, HGetAll reads them all into a HashMap, and from there it is one line to fill your own struct. Every value comes back as a string, so convert numbers yourself.
import db.redis
import conv
struct Link:
pub id: i64 = 0
pub url: str = ""
pub title: str = ""
pub owner: str = ""
end
func save(c: redis.Client, url: str, title: str, owner: str): i64:
id := redis.Incr(c, "links:next")
key := "link:" + conv.FormatInt(id)
redis.HSet(c, key, "url", url)
redis.HSet(c, key, "title", title)
redis.HSet(c, key, "owner", owner)
ret id
end
func load(c: redis.Client, id: i64): Link:
m := redis.HGetAll(c, "link:" + conv.FormatInt(id))
if m.size() == 0:
ret Link {}
end
ret Link { id = id, url = m.get("url"), title = m.get("title"), owner = m.get("owner") }
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
save(c, "https://salamlang.ir", "Salam home", "sara")
save(c, "https://salamlang.ir/learn/", "Salam course", "omid")
save(c, "https://github.com/SalamLang/Salam", "Salam on GitHub", "sara")
repeat 3 in i:
l := load(c, (i + 1) as i64)
println l.id, l.title, "->", l.url, "by", l.owner
end
println "link 9 found:", load(c, 9).id != 0
println "fields of link:2:", redis.HLen(c, "link:2")
println "owner of link:2:", redis.HGet(c, "link:2", "owner")
redis.HSet(c, "link:2", "note", "for new students")
println "has note:", redis.HExists(c, "link:2", "note")
println "note removed:", redis.HDel(c, "link:2", "note")
println "has note:", redis.HExists(c, "link:2", "note")
endHGetAll returns an empty map for a key that does not exist, which is how load tells a missing link apart. To write several fields in one round trip, send HSET key f1 v1 f2 v2 with Command, as step 9 does.
4. Counters
Incr, IncrBy and Decr change a number stored as a string and return the new value. Redis runs each command alone, so two programs counting the same key never lose an update. A key that does not exist counts as 0, so there is nothing to set up first. The value is still a string: Get returns its digits and conv.ParseInt turns them into a number.
import db.redis
import conv
func click(c: redis.Client, id: int): i64:
ret redis.Incr(c, "clicks:" + conv.FormatInt(id as i64))
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
click(c, 1)
click(c, 1)
click(c, 3)
click(c, 3)
println "link 1 clicks:", click(c, 1)
println "link 2 imported clicks:", redis.IncrBy(c, "clicks:2", 40)
println "link 2 after a refund:", redis.IncrBy(c, "clicks:2", -2)
println "link 3 after a bot click is removed:", redis.Decr(c, "clicks:3")
raw := redis.Get(c, "clicks:2")
println "stored as text:", raw, "type", redis.Type(c, "clicks:2")
n := conv.ParseInt(raw)
println "as a number plus one:", n + 1
println "next link id would be:", conv.ParseInt(redis.Get(c, "links:next")) + 1
end5. Lists
A list keeps strings in order. LPush adds to the front and RPush to the end; LPop and RPop take one off. LRange reads a slice by position, both ends included, and negative positions count from the end, so 0, -1 is the whole list. Pushing new ids to the front gives a newest first feed.
import db.redis
import conv
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
repeat 3 in i:
redis.LPush(c, "links:recent", conv.FormatInt((i + 1) as i64))
end
println "recent count:", redis.LLen(c, "links:recent")
each id in redis.LRange(c, "links:recent", 0, -1):
println " link", id, redis.HGet(c, "link:" + id, "title")
end
println "newest two:", redis.LRange(c, "links:recent", 0, 1).len()
println "oldest:", redis.LRange(c, "links:recent", -1, -1).get(0)
redis.RPush(c, "links:review", "2")
redis.RPush(c, "links:review", "3")
println "review first:", redis.LPop(c, "links:review")
println "review last:", redis.RPop(c, "links:review")
println "review empty: [" + redis.LPop(c, "links:review") + "]", redis.LLen(c, "links:review")
endA list that becomes empty is deleted, and popping from it returns an empty string. To keep a feed at a fixed length, follow each push with LTRIM links:recent 0 99 through Command.
6. Sets
A set holds each member once. SAdd returns 1 for a new member and 0 for one that was already there, which makes it a one step "have I seen this?" check. SCard counts the members, SIsMember tests one and SMembers returns them all.
import db.redis
import sort
func visit(c: redis.Client, link: str, user: str): bool:
ret redis.SAdd(c, "visitors:" + link, user) == 1
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
println "sara first visit:", visit(c, "1", "sara")
println "omid first visit:", visit(c, "1", "omid")
println "sara again:", visit(c, "1", "sara")
visit(c, "1", "reza")
visit(c, "3", "sara")
println "unique visitors of link 1:", redis.SCard(c, "visitors:1")
println "reza saw link 3:", redis.SIsMember(c, "visitors:3", "reza")
println "reza removed:", redis.SRem(c, "visitors:1", "reza")
names := redis.SMembers(c, "visitors:1")
sort.Sort(names)
each n in names:
println " -", n
end
endRedis does not keep sets in any order, so sort what SMembers returns before you show it. On a set with millions of members, use SSCAN through Command instead.
7. Expiry
Any key can be given a lifetime. SetEx stores a string and its lifetime in one command; Expire adds one to a key that already exists. TTL returns the seconds left, -1 for a key that never expires and -2 for one that is not there. When the time is up Redis deletes the key by itself.
import db.redis
import time
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
redis.SetEx(c, "preview:3", "<b>Salam on GitHub</b>", 600)
println "preview ttl:", redis.TTL(c, "preview:3")
println "link ttl:", redis.TTL(c, "link:3")
println "missing ttl:", redis.TTL(c, "link:99")
redis.Set(c, "link:3:promo", "half price")
println "promo expires:", redis.Expire(c, "link:3:promo", 1)
println "promo now:", redis.Get(c, "link:3:promo")
time.Sleep(1500)
println "promo later: [" + redis.Get(c, "link:3:promo") + "]", redis.Exists(c, "link:3:promo")
println "expire a missing key:", redis.Expire(c, "link:3:promo", 10)
redis.Set(c, "preview:3", "<b>new preview</b>")
println "ttl after Set:", redis.TTL(c, "preview:3")
endWatch the last line: a plain Set on a key removes its lifetime. Use SetEx again when you replace a value that should keep expiring.
8. Any command: a leaderboard
Redis has about two hundred commands and this package has a function for the most common ones. Command sends any of them: put the command name and its arguments in a Vector<str>. The Reply you get back has a type (REPLY_STRING, REPLY_INTEGER, REPLY_ARRAY, REPLY_NIL, REPLY_STATUS or REPLY_ERROR), and you read it with ReplyStr, ReplyInt, ReplyArrayLen and ReplyArrayGet. This step uses a sorted set, which has no function of its own, to rank the links by clicks.
import db.redis
import conv
func run(c: redis.Client, words: str[]): redis.Reply:
mut args := Vector {} as Vector<str>
each w in words:
args.push(w)
end
ret redis.Command(c, args)
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
repeat 3 in i:
id := conv.FormatInt((i + 1) as i64)
r := run(c, ["ZADD", "links:top", redis.Get(c, "clicks:" + id), id])
redis.FreeReply(r)
end
top := run(c, ["ZREVRANGE", "links:top", "0", "-1", "WITHSCORES"])
if redis.ReplyType(top) == redis.REPLY_ARRAY:
mut i := 0
until i < redis.ReplyArrayLen(top):
id := redis.ReplyStr(redis.ReplyArrayGet(top, i))
score := redis.ReplyStr(redis.ReplyArrayGet(top, i + 1))
println score, "clicks:", redis.HGet(c, "link:" + id, "title")
i += 2
end
end
redis.FreeReply(top)
rank := run(c, ["ZREVRANK", "links:top", "1"])
println "link 1 rank:", redis.ReplyInt(rank) + 1, "integer reply:", redis.ReplyType(rank) == redis.REPLY_INTEGER
redis.FreeReply(rank)
many := run(c, ["HMGET", "link:1", "title", "owner", "color"])
repeat redis.ReplyArrayLen(many) in i:
e := redis.ReplyArrayGet(many, i)
println " field", i, "[" + redis.ReplyStr(e) + "]", "nil:", redis.ReplyIsNil(e)
end
redis.FreeReply(many)
endFree every reply from Command with FreeReply. The elements from ReplyArrayGet belong to their array and are freed with it, so never free them on their own. A field that is missing comes back as a nil element, as color does above.
9. Transactions
MULTI starts a transaction: the commands after it answer QUEUED, and EXEC runs them all at once, with nothing from other clients in between. DISCARD throws the queue away. Redis does not roll back: if one queued command fails, the others still run, so check each element of the EXEC reply when that matters.
import db.redis
import conv
func run(c: redis.Client, words: str[]): str:
mut args := Vector {} as Vector<str>
each w in words:
args.push(w)
end
r := redis.Command(c, args)
mut out := redis.ReplyStr(r)
if redis.ReplyType(r) == redis.REPLY_ARRAY:
out = "array of " + conv.FormatInt(redis.ReplyArrayLen(r) as i64)
else if redis.ReplyIsNil(r):
out = "nil"
else if redis.ReplyType(r) == redis.REPLY_INTEGER:
out = conv.FormatInt(redis.ReplyInt(r))
end
redis.FreeReply(r)
ret out
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
println run(c, ["MULTI"])
println run(c, ["INCR", "links:next"])
println run(c, ["HSET", "link:4", "url", "https://salamlang.ir/std/", "title", "Salam std", "owner", "omid"])
println run(c, ["LPUSH", "links:recent", "4"])
println run(c, ["EXEC"])
println "link 4:", redis.HGet(c, "link:4", "title"), "next id:", redis.Get(c, "links:next")
other := redis.Connect("127.0.0.1", 6379)
defer redis.Close(other)
redis.Auth(other, "s3cret")
println run(c, ["WATCH", "link:4"])
title := redis.HGet(c, "link:4", "title")
redis.HSet(other, "link:4", "title", "Salam standard library")
println run(c, ["MULTI"])
println run(c, ["HSET", "link:4", "title", title + " (checked)"])
println "exec after a change:", run(c, ["EXEC"])
println "title kept:", redis.HGet(c, "link:4", "title")
println run(c, ["MULTI"])
println run(c, ["INCR", "clicks:4"])
println run(c, ["DISCARD"])
println "clicks:4 exists:", redis.Exists(c, "clicks:4")
endWATCH adds optimistic locking. If another client changes a watched key before EXEC, the transaction does not run and EXEC answers nil; read the key again and retry. The second client here plays that other user. A transaction lives on its connection, so keep MULTI through EXEC on one client and one thread.
10. Errors
There are two kinds of failure. The server can refuse a command, for a wrong type or a bad argument; the connection stays fine and the reply has type REPLY_ERROR with the message in ReplyStr. The shortcut functions do not show that message: Get on a hash returns an empty string and Incr on text returns 0. When you need to know, send the command with Command. The other kind is a lost connection, which this step causes on purpose with CLIENT KILL from a second client.
import db.redis
func run(c: redis.Client, words: str[]): str:
mut args := Vector {} as Vector<str>
each w in words:
args.push(w)
end
r := redis.Command(c, args)
mut out := "ok"
if not redis.ReplyOk(r):
out = "no reply: " + redis.Error(c)
else if redis.ReplyIsError(r):
out = "server error: " + redis.ReplyStr(r)
end
redis.FreeReply(r)
ret out
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
println "Get on a hash: [" + redis.Get(c, "link:1") + "]"
println "Incr on text:", redis.Incr(c, "site:name")
println run(c, ["GET", "link:1"])
println run(c, ["INCR", "site:name"])
println run(c, ["HGET", "link:1"])
println "connection still fine:", redis.Ping(c), "[" + redis.Error(c) + "]"
admin := redis.Connect("127.0.0.1", 6379)
defer redis.Close(admin)
redis.Auth(admin, "s3cret")
println run(admin, ["CLIENT", "KILL", "TYPE", "normal", "SKIPME", "yes"])
println "ping after the kill:", redis.Ping(c)
println "Ok:", redis.Ok(c), "Error:", redis.Error(c)
println run(c, ["PING"])
endAfter the connection is gone Ok still says true, because it only reports whether Connect worked. Error now holds the reason, every call returns its empty value and Command returns a reply that is not ReplyOk. The client does not reconnect by itself: close it and call Connect again.
11. Several threads
A Client is one connection with one read buffer, so two threads must never use the same client at once. Give each thread its own, as below. There is no built in pool; for a server that handles many requests, open one client per worker thread and keep it for the life of the thread. The counter comes out exact because Redis applies each INCR alone.
import db.redis
const WORKERS := 4
const CLICKS := 250
func worker():
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
repeat CLICKS:
redis.Incr(c, "clicks:4")
redis.SAdd(c, "visitors:4", "bot")
end
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
redis.Del(c, "clicks:4")
mut threads := Vector {} as Vector<i64>
repeat WORKERS:
threads.push(spawn(worker))
end
each t in threads:
join(t)
end
println "clicks on link 4:", redis.Get(c, "clicks:4"), "expected", WORKERS * CLICKS
println "unique visitors:", redis.SCard(c, "visitors:4")
end12. Look around and clean up
Keys lists the keys matching a pattern and Type says what each one holds. Keys walks the whole database in one go and blocks the server meanwhile, which is fine here and a problem on a production server with millions of keys. SCAN walks it a page at a time with a cursor; the loop below runs it through Command. FlushDB deletes every key in the current database.
import db.redis
import sort
func scan(c: redis.Client, pattern: str): Vector<str>:
mut found := Vector {} as Vector<str>
mut cursor := "0"
until true:
mut args := Vector {} as Vector<str>
args.push("SCAN")
args.push(cursor)
args.push("MATCH")
args.push(pattern)
args.push("COUNT")
args.push("100")
r := redis.Command(c, args)
cursor = redis.ReplyStr(redis.ReplyArrayGet(r, 0))
page := redis.ReplyArrayGet(r, 1)
repeat redis.ReplyArrayLen(page) in i:
found.push(redis.ReplyStr(redis.ReplyArrayGet(page, i)))
end
redis.FreeReply(r)
if cursor == "0":
break
end
end
sort.Sort(found)
ret found
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
keys := redis.Keys(c, "*")
sort.Sort(keys)
each k in keys:
println k, redis.Type(c, k)
end
old := scan(c, "visitors:*")
each k in old:
redis.Del(c, k)
end
println "deleted", old.len(), "visitor sets, left:", scan(c, "visitors:*").len()
println "flush:", redis.FlushDB(c), "keys left:", redis.Keys(c, "*").len()
endMore
Publishsends a message to every client subscribed to a channel and returns how many got it. This package can publish but cannot subscribe:Commandreads one reply per command, and a subscriber needs to wait for messages that arrive on their own. For work that must not be lost, use a list as a queue (see below).- For an ACL user with a name, send
AUTH name passwordwithCommand;Authsends only the password. - Commands such as
HINCRBY,SETNX,MGET,LTRIM,ZADDandXADDall go throughCommandin the same way as step 8. - The connection waits as long as the server takes. Commands that block on purpose, such as
BLPOP, take their own timeout argument.
Guide: cache a slow page
The most common use of Redis is a cache in front of something slow. Look for the key first; on a miss, build the value and store it with SetEx, so a stale copy disappears by itself. When the source changes, delete the key and the next request rebuilds it. Exists rather than an empty Get decides the hit, so an empty page can be cached too.
import db.redis
mut renders := 0
func render(id: str): str:
renders += 1
ret "<h1>Note " + id + "</h1>"
end
func page(c: redis.Client, id: str): str:
key := "cache:note:" + id
if redis.Exists(c, key):
ret redis.Get(c, key)
end
html := render(id)
redis.SetEx(c, key, html, 60)
ret html
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
println page(c, "42")
println page(c, "42")
println page(c, "7")
println "rendered", renders, "times for 3 requests"
println "seconds left:", redis.TTL(c, "cache:note:42")
redis.Del(c, "cache:note:42")
println page(c, "42")
println "rendered", renders, "times after the edit"
redis.Del(c, "cache:note:42")
redis.Del(c, "cache:note:7")
endGuide: limit requests per user
A fixed window rate limiter needs one counter per user. Incr returns the count, and the first request of a window sets the lifetime, so the counter resets by itself when the window ends. Because Incr is atomic, the limit holds even when many server processes check the same user.
import db.redis
func allow(c: redis.Client, user: str, limit: i64, seconds: int): bool:
key := "rate:" + user
n := redis.Incr(c, key)
if n == 1:
redis.Expire(c, key, seconds)
end
ret n <= limit
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
repeat 5 in i:
println "sara request", i + 1, "allowed:", allow(c, "sara", 3, 60)
end
println "omid request 1 allowed:", allow(c, "omid", 3, 60)
println "sara window ends in:", redis.TTL(c, "rate:sara"), "seconds"
redis.Del(c, "rate:sara")
redis.Del(c, "rate:omid")
endGuide: a job queue with a worker
A list makes a simple job queue: producers RPush jobs and a worker takes them from the front. LPop returns at once, so a worker that polls it wastes time. BLPOP, sent through Command, waits until a job arrives or the timeout in seconds runs out, and answers nil on a timeout. The worker runs in its own thread with its own client, as step 11 requires.
import db.redis
func take(c: redis.Client, queue: str, seconds: str): str:
mut args := Vector {} as Vector<str>
args.push("BLPOP")
args.push(queue)
args.push(seconds)
r := redis.Command(c, args)
mut job := ""
if redis.ReplyType(r) == redis.REPLY_ARRAY:
job = redis.ReplyStr(redis.ReplyArrayGet(r, 1))
end
redis.FreeReply(r)
ret job
end
func worker():
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
mut job := take(c, "queue:email", "1")
until job.len() > 0:
redis.RPush(c, "done:email", "sent " + job)
job = take(c, "queue:email", "1")
end
end
func main:
c := redis.Connect("127.0.0.1", 6379)
defer redis.Close(c)
redis.Auth(c, "s3cret")
t := spawn(worker)
redis.RPush(c, "queue:email", "welcome:sara")
redis.RPush(c, "queue:email", "welcome:omid")
redis.RPush(c, "queue:email", "digest:all")
println "queued 3 jobs"
join(t)
each line in redis.LRange(c, "done:email", 0, -1):
println line
end
println "left in queue:", redis.LLen(c, "queue:email")
redis.Del(c, "done:email")
endA job popped by a worker that then crashes is lost. When that matters, move each job to a processing list with LMOVE and remove it only after the work is done.
Which function for which job
| Job | Use |
|---|---|
| Cache a value for a while | SetEx, then Get or Exists |
| Count views, hand out ids | Incr, IncrBy, Decr |
| Store a record or a session | HSet, HGetAll, then Expire |
| Recent items, a job queue | LPush, LRange, RPush, LPop, BLPOP through Command |
| Unique visitors, tags, who is online | SAdd, SCard, SIsMember |
| Rankings, scores | ZADD, ZREVRANGE, ZINCRBY through Command |
| Several writes together | MULTI and EXEC through Command |
| Tell another service something happened | Publish |
| Find keys on a big server | SCAN through Command, not Keys |
Types
Client
struct Client| Name | Type | Description |
|---|---|---|
handle | void* | The connection pointer; null when Connect failed. |
import db.redis
func main:
c := redis.Connect("127.0.0.1", 6379)
if not redis.Ok(c):
println redis.Error(c)
ret
end
_ := redis.Auth(c, "s3cret")
_ := redis.Select(c, 1)
println redis.Ping(c)
_ := redis.Set(c, "greeting", "hello")
_ := redis.SetEx(c, "session:42", "token", 3600)
println redis.Get(c, "greeting"), redis.Exists(c, "greeting"), redis.TTL(c, "session:42")
println redis.Incr(c, "visits"), redis.IncrBy(c, "visits", 10), redis.Decr(c, "visits")
println redis.Append(c, "greeting", "!"), redis.StrLen(c, "greeting"), redis.Type(c, "greeting")
_ := redis.Expire(c, "greeting", 60)
println redis.Keys(c, "session:*").len()
_ := redis.Del(c, "greeting")
_ := redis.HSet(c, "user:1", "name", "Maryam")
_ := redis.HSet(c, "user:1", "city", "Tehran")
println redis.HGet(c, "user:1", "name"), redis.HExists(c, "user:1", "age"), redis.HLen(c, "user:1")
all := redis.HGetAll(c, "user:1")
println all.size()
_ := redis.HDel(c, "user:1", "city")
_ := redis.RPush(c, "jobs", "a")
_ := redis.LPush(c, "jobs", "b")
println redis.LLen(c, "jobs"), redis.LRange(c, "jobs", 0, -1).len()
println redis.LPop(c, "jobs"), redis.RPop(c, "jobs")
_ := redis.SAdd(c, "tags", "go")
_ := redis.SAdd(c, "tags", "salam")
println redis.SIsMember(c, "tags", "salam"), redis.SCard(c, "tags"), redis.SMembers(c, "tags").len()
_ := redis.SRem(c, "tags", "go")
println redis.Publish(c, "news", "fresh news")
mut args := Vector {} as Vector<str>
args.push("GET")
args.push("visits")
r := redis.Command(c, args)
if redis.ReplyOk(r) and not redis.ReplyIsError(r) and not redis.ReplyIsNil(r):
println redis.ReplyType(r) == redis.REPLY_STRING, redis.ReplyStr(r)
end
redis.FreeReply(r)
mut args2 := Vector {} as Vector<str>
args2.push("LRANGE")
args2.push("jobs")
args2.push("0")
args2.push("-1")
r2 := redis.Command(c, args2)
if redis.ReplyType(r2) == redis.REPLY_ARRAY:
repeat redis.ReplyArrayLen(r2) in i:
println redis.ReplyStr(redis.ReplyArrayGet(r2, i))
end
end
println redis.ReplyInt(r2), redis.REPLY_INTEGER, redis.REPLY_NIL, redis.REPLY_STATUS, redis.REPLY_ERROR, redis.REPLY_DOUBLE
redis.FreeReply(r2)
_ := redis.FlushDB(c)
redis.Close(c)
endReply
struct Reply| Name | Type | Description |
|---|---|---|
handle | void* | The reply pointer. |
The example for this item is shown with the ones above: db-redis-Client.salam
Constants and variables
REPLY_STRING
const REPLY_STRING: i32 = 1The example for this item is shown with the ones above: db-redis-Client.salam
REPLY_ARRAY
const REPLY_ARRAY: i32 = 2The example for this item is shown with the ones above: db-redis-Client.salam
REPLY_INTEGER
const REPLY_INTEGER: i32 = 3The example for this item is shown with the ones above: db-redis-Client.salam
REPLY_NIL
const REPLY_NIL: i32 = 4The example for this item is shown with the ones above: db-redis-Client.salam
REPLY_STATUS
const REPLY_STATUS: i32 = 5OK.The example for this item is shown with the ones above: db-redis-Client.salam
REPLY_ERROR
const REPLY_ERROR: i32 = 6The example for this item is shown with the ones above: db-redis-Client.salam
REPLY_DOUBLE
const REPLY_DOUBLE: i32 = 7ReplyStr. Only a server switched to RESP3 sends it; over the default protocol floats arrive as strings.The example for this item is shown with the ones above: db-redis-Client.salam
Functions
Connect
func Connect(host: str, port: int): ClientOk.The example for this item is shown with the ones above: db-redis-Client.salam
Ok
func Ok(c: Client): boolConnect worked. It stays true after the connection is lost later; Error or Ping tell you that.The example for this item is shown with the ones above: db-redis-Client.salam
Error
func Error(c: Client): strno connection when Connect failed, or a message such as Server closed the connection once it broke; empty while it works. Errors the server sends back for one command are not kept here.The example for this item is shown with the ones above: db-redis-Client.salam
Close
func Close(c: Client)The example for this item is shown with the ones above: db-redis-Client.salam
Ping
func Ping(c: Client): boolThe example for this item is shown with the ones above: db-redis-Client.salam
Select
func Select(c: Client, index: int): boolThe example for this item is shown with the ones above: db-redis-Client.salam
Auth
func Auth(c: Client, password: str): boolAUTH name password with Command.The example for this item is shown with the ones above: db-redis-Client.salam
FlushDB
func FlushDB(c: Client): boolThe example for this item is shown with the ones above: db-redis-Client.salam
HSet
func HSet(c: Client, key: str, field: str, val: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
HGet
func HGet(c: Client, key: str, field: str): strThe example for this item is shown with the ones above: db-redis-Client.salam
HDel
func HDel(c: Client, key: str, field: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
HExists
func HExists(c: Client, key: str, field: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
HLen
func HLen(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
HGetAll
func HGetAll(c: Client, key: str): HashMap<str, str>The example for this item is shown with the ones above: db-redis-Client.salam
Set
func Set(c: Client, key: str, val: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
SetEx
func SetEx(c: Client, key: str, val: str, seconds: int): boolThe example for this item is shown with the ones above: db-redis-Client.salam
Get
func Get(c: Client, key: str): strThe example for this item is shown with the ones above: db-redis-Client.salam
Exists
func Exists(c: Client, key: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
Del
func Del(c: Client, key: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
Expire
func Expire(c: Client, key: str, seconds: int): boolThe example for this item is shown with the ones above: db-redis-Client.salam
TTL
func TTL(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
Incr
func Incr(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
IncrBy
func IncrBy(c: Client, key: str, delta: i64): i64The example for this item is shown with the ones above: db-redis-Client.salam
Decr
func Decr(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
Append
func Append(c: Client, key: str, val: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
StrLen
func StrLen(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
Type
func Type(c: Client, key: str): strstring or hash; none when there is no such key.The example for this item is shown with the ones above: db-redis-Client.salam
Keys
func Keys(c: Client, pattern: str): Vector<str>user:*. It blocks the server while it walks every key, so use SCAN through Command on a large database.The example for this item is shown with the ones above: db-redis-Client.salam
SAdd
func SAdd(c: Client, key: str, member: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
SRem
func SRem(c: Client, key: str, member: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
SIsMember
func SIsMember(c: Client, key: str, member: str): boolThe example for this item is shown with the ones above: db-redis-Client.salam
SCard
func SCard(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
SMembers
func SMembers(c: Client, key: str): Vector<str>The example for this item is shown with the ones above: db-redis-Client.salam
Publish
func Publish(c: Client, channel: str, message: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
Command
func Command(c: Client, args: Vector<str>): ReplyFreeReply; the reply is not ReplyOk when the connection is down.The example for this item is shown with the ones above: db-redis-Client.salam
ReplyOk
func ReplyOk(r: Reply): boolThe example for this item is shown with the ones above: db-redis-Client.salam
ReplyType
func ReplyType(r: Reply): intThe example for this item is shown with the ones above: db-redis-Client.salam
ReplyIsError
func ReplyIsError(r: Reply): boolReplyStr holds its message.The example for this item is shown with the ones above: db-redis-Client.salam
ReplyIsNil
func ReplyIsNil(r: Reply): boolGET on a key that is not there.The example for this item is shown with the ones above: db-redis-Client.salam
ReplyInt
func ReplyInt(r: Reply): i64The example for this item is shown with the ones above: db-redis-Client.salam
ReplyStr
func ReplyStr(r: Reply): strThe example for this item is shown with the ones above: db-redis-Client.salam
ReplyArrayLen
func ReplyArrayLen(r: Reply): intThe example for this item is shown with the ones above: db-redis-Client.salam
ReplyArrayGet
func ReplyArrayGet(r: Reply, i: int): ReplyThe example for this item is shown with the ones above: db-redis-Client.salam
FreeReply
func FreeReply(r: Reply)Command, with all its elements.The example for this item is shown with the ones above: db-redis-Client.salam
LPush
func LPush(c: Client, key: str, val: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
RPush
func RPush(c: Client, key: str, val: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
LPop
func LPop(c: Client, key: str): strThe example for this item is shown with the ones above: db-redis-Client.salam
RPop
func RPop(c: Client, key: str): strThe example for this item is shown with the ones above: db-redis-Client.salam
LLen
func LLen(c: Client, key: str): i64The example for this item is shown with the ones above: db-redis-Client.salam
LRange
func LRange(c: Client, key: str, start: int, stop: int): Vector<str>start to stop, both included. Negative positions count from the end, so 0, -1 is the whole list.The example for this item is shown with the ones above: db-redis-Client.salam